An ATProto OAuth client whose loopback client_id inlines its client metadata (http://localhost?redirect_uri=…&scope=…, RFC 8252 style) produces an authorize URL of 4039 characters. The request line alone is 4055 bytes, and zds answers any request whose header block exceeds 4096 bytes with 431 and the body Request header is too big, so no browser can fetch the URL — Chrome adds several hundred bytes of headers before the form is even reached.
Measured URL (throwaway local stack, 4039 chars; request_uri is single-use and already spent):
http://localhost:3002/oauth/authorize?client_id=http%3A%2F%2Flocalhost%3Fredirect_uri%3Dhttp%253A%252F%252F127.0.0.1%253A5179%252Foauth%252Fcallback%26scope%3Datproto%2Brepo%253Anet.olamaelcu.livtet.actor.profile%2Brepo%253Anet.olamaelcu.livtet.biblio.readingGoal%2Brepo%253Anet.olamaelcu.livtet.biblio.shelf%2Brepo%253Anet.olamaelcu.livtet.biblio.bookShelving%2Brepo%253Anet.olamaelcu.livtet.biblio.shelfItem%2Brepo%253Abuzz.bookhive.book%2Brepo%253Anet.olamaelcu.livtet.biblio.follow%2Brepo%253Anet.olamaelcu.livtet.biblio.bookReview%2Brepo%253Acommunity.lexicon.book.review%2Brepo%253Anet.olamaelcu.livtet.topBook%2Brepo%253Anet.olamaelcu.livtet.bookReview%2Bblob%253Aimage%252F*%2Brpc%253Acom.atproto.repo.getRecord%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Acommunity.lexicon.book.searchEditions%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Acommunity.lexicon.book.searchContributors%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Anet.olamaelcu.livtet.biblio.getBooks%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Anet.olamaelcu.livtet.biblio.getEditionsByContributor%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Anet.olamaelcu.livtet.biblio.getImageForBook%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Anet.olamaelcu.livtet.biblio.getImageForContributor%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Anet.olamaelcu.livtet.biblio.getReviewsByBook%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Anet.olamaelcu.livtet.biblio.getShelversOfBook%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Anet.olamaelcu.livtet.biblio.getShelvingOfBook%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Anet.olamaelcu.livtet.biblio.listBooksOnShelf%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Anet.olamaelcu.livtet.biblio.listRecentShelving%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Anet.olamaelcu.livtet.biblio.listReviewsByActor%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Anet.olamaelcu.livtet.biblio.listShelves%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Anet.olamaelcu.livtet.biblio.listShelfItems%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Anet.olamaelcu.livtet.biblio.listShelfItemsByBook%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Anet.olamaelcu.livtet.biblio.listShelvesWithBooks%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_pds%2Brpc%253Acom.atproto.feed.describeFeedGenerator%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_feed%2Brpc%253Acom.atproto.feed.getFeedSkeleton%253Faud%253Ddid%253Aweb%253Abiblio.livtet.olamaelcu.net%252523atproto_feed%2Bspace%253Anet.olamaelcu.livtet.readingList%253Fauthority%253Dself%2526collection%253D*%2526action%253Dcreate%2526action%253Dupdate%2526action%253Ddelete%2526manage%253Dcreate%2526manage%253Dupdate%2526manage%253Ddelete%2Bspace%253Anet.olamaelcu.livtet.readingList%253Fauthority%253D*%2526action%253Dread%2Bspace%253Anet.olamaelcu.livtet.bookClub%253Fauthority%253Dself%2526collection%253D*%2526action%253Dcreate%2526action%253Dupdate%2526action%253Ddelete%2526manage%253Dcreate%2526manage%253Dupdate%2526manage%253Ddelete%2Bspace%253Anet.olamaelcu.livtet.bookClub%253Fauthority%253D*%2526action%253Dread%2Bspace%253Anet.olamaelcu.livtet.clubBookmarks%253Fauthority%253Dself%2526collection%253D*%2526action%253Dcreate%2526action%253Dupdate%2526action%253Ddelete%2526action%253Dread%2526manage%253Dcreate%2526manage%253Dupdate%2526manage%253Ddelete&request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3Ae0a1a8aa8fdedbd8808a3ce0530d397c
Bisection on the header block itself: 4096 bytes → 200, 4097 → 431 (curl -H "X-Pad: …" against a fixed route; curl --trace-ascii prints Send header, 4096 bytes on the accepted side). With curl's default headers the URL above returns 200; with browser-like headers (UA, Accept, Accept-Language, Accept-Encoding, Sec-Fetch-*, sec-ch-ua, Upgrade-Insecure-Requests) it returns 431 / Request header is too big.
Expected: an allowance sized for real browsers (nginx defaults to 8k per header line and 16k total; Chrome routinely sends a few hundred bytes to ~1 KB of headers), or a documented, configurable cap.
Impact: sign-in is impossible for clients that build such URLs, and the failure presents as a stalled authorization page (a Playwright walker waited the full 5-minute test timeout for a password field) rather than as a server-side limit.
thanks for the issue will take a look!