Skip to content

feat(web): add Sign in with ChatGPT and an OpenAI BYOK connection (part 1/1) - #6255

Merged
iscekic merged 10 commits into
mainfrom
kwf/owner-signin-with-chatgpt-dc3c-l1
Sep 18, 2026
Merged

iscekic merged 10 commits into
mainfrom
kwf/owner-signin-with-chatgpt-dc3c-l1

Conversation

@iscekic

@iscekic iscekic commented Sep 17, 2026 •

Copy link
Copy Markdown
Collaborator

Changelog for users

  • Web sign-in and sign-up offer "Sign in with ChatGPT" beside the existing methods.
  • A verified ChatGPT account signs in to the matching existing Kilo account; an unknown account creates a new one.
  • The BYOK page gains an "OpenAI (ChatGPT subscription)" card with a clear connected and disconnected state.
  • Connecting and disconnecting each take one click, with no API key pasted.
  • While a connection is enabled, eligible OpenAI model requests use it ahead of other billing paths.
  • A connection that fails or expires shows a readable reconnect message and a reconnect action.
  • Unlinking ChatGPT removes the stored connection, and the pasted OpenAI key entry is relabelled "OpenAI API key".

Changelog for maintainers

  • OPENAI_CLIENT_ID and OPENAI_CLIENT_SECRET come from the environment; the secret is server-side only and sent as HTTP Basic. Optional OPENAI_DISCOVERY_URL, OPENAI_TOKEN_ENDPOINT and OPENAI_CHATGPT_API_URL overrides exist, and no client-id literal is written into source.
  • A new openai NextAuth provider validates the ID token through discovery with PKCE, state and nonce checks, and uses the registered redirect <app origin>/testing/oai-redirect. A new route rewrites that path to the NextAuth callback and returns a declined or failed connect to /byok for a signed-in person.
  • The provider account id is the issuer-qualified <issuer>#<sub>. Re-linking the identical id is a no-op; a different id still returns PROVIDER-ALREADY-LINKED. Unlinking the provider deletes the stored connection.
  • The delegated token pair is stored encrypted in byok_api_keys under provider id openai-chatgpt, refreshes single-flight under a row lock, and a terminal refresh failure clears the tokens and disables the row. The status and disconnect procedures return only state and identity, never a token.
  • getProvider prefers an enabled connection for eligible responses requests and keeps the normal balance and abuse checks; a terminally dead connection returns chatgpt-reconnect instead of another billing path. The partner key is OPENAI_API_KEY, and the delegated token travels in OpenAI-On-Behalf-Of-Token.
  • Review first: refresh logs redact tokens and the client secret, the refresh race (single-flight plus row lock), and the fail-readable choice for a dead connection.
  • The E2E proof below is stack-wide, captured at the tip.
  • Scenarios that need the owner-supplied ChatGPT account are skipped, pending owner verification; those flows ran against a local stub issuer.

E2E proof

[e1] web: the sign-in page offers ChatGPT beside the existing methods - open /users/sign_in and the OAuth group shows 'Sign in with ChatGPT' together with 'Continue with Google' and 'Continue with Email'. — probe-auth.log: 'e1 url=http://localhost:6900/users/sign_in hasChatGPT=true hasGoogle=true hasEmail=true buttons=[... "Continue with Email", ... "Sign in with ChatGPT"]' (screenshot shots/e1-signin.png).

web: the sign-in page offers ChatGPT beside the existing methods - open /users/sign_in and the OAuth group shows 'Sign in with ChatGPT' together with 'Continue with Google' and 'Continue with Email'. — e1-signin.png

[e1] web: signing in returns the same person to their existing account, never a duplicate - precondition: a Kilo account exists whose email matches the ChatGPT account, needs:seed; complete the Sign in… — Consent completed in the browser and the session resolved to the account that already existed: e1-signin.log 'LANDED_URL http://localhost:6900/profile' with '"kiloUserId":"d72f6758-7d2d-4a61-bbe2-4fe6fe9f2f60"' and '"isNewUser":false'; e1-pre-db.log holds that pre-existing id ('d72f6758-7d2d-4a61-bbe2-4fe6fe9f2f60|verify-byok-1789588278700@example.com', count '1') and e1-post-db.log still shows one row for the email plus 'openai|https://auth.openai.com#vf-e1-sub|d72f6758-7d2d-4a61-bbe2-4fe6fe9f2f60'; no real ChatGPT account is supplied, so the consent ran against a local stub issuer mirroring…

web: signing in returns the same person to their existing account, never a duplicate - precondition: a Kilo account exists whose email matches the ChatGPT account, needs:seed; complete the Sign in… — e1-signin-2-landed.png

[e2] web: the sign-up page offers the same login/signup option - open /users/sign_in?signup=true and the OAuth button group shows 'Sign in with ChatGPT' beside 'Continue with Google'. — /users/sign_in?signup=true renders 'Sign in with ChatGPT' between 'Continue with Discord' and 'Continue with Email', beside 'Continue with Google'.

web: the sign-up page offers the same login/signup option - open /users/sign_in?signup=true and the OAuth button group shows 'Sign in with ChatGPT' beside 'Continue with Google'. — e2-signup.png

[e2] web: a person with no account gets a new one - complete the Sign in with ChatGPT consent with a supplied ChatGPT account whose email has no Kilo user (needs the account +… — A ChatGPT identity with no Kilo account created one: e2-pre-db.log '0' users with the email before, e2-signin.log '"isNewUser":true' with '"kiloUserId":"6d4d26f5-84a9-4824-9abb-c30cfe87dfcc"', and e2-post-db.log '6d4d26f5-84a9-4824-9abb-c30cfe87dfcc|vf-oai-new-1789603494@example.com' plus 'openai|https://auth.openai.com#vf-e2-sub|6d4d26f5-84a9-4824-9abb-c30cfe87dfcc' (stub issuer as in e1); screenshots e2-signin-1-start.png, e2-signin-2-landed.png.

[e2] web: a person with no account gets a new one - complete the Sign in with ChatGPT consent with a supplied ChatGPT account whose email has no Kilo user (needs the account +… — prior/e2-signin-1-start.png

[e2] web: a person with no account gets a new one - complete the Sign in with ChatGPT consent with a supplied ChatGPT account whose email has no Kilo user (needs the account +…

web: a person with no account gets a new one - complete the Sign in with ChatGPT consent with a supplied ChatGPT account whose email has no Kilo user (needs the account +… — e2-signin-2-landed.png

[e4] web: an untrusted callback creates no session and no account - replay GET /testing/oai-redirect?code=not-a-real-code&state= in a context holding the transaction… — with no transaction cookies the replay lands on /users/sign_in?callbackUrl=…&error=OAuthCallback showing 'Error / Oops! Something went wrong trying to log in.', /api/auth/session returns {}, and db.sh gives user_auth_provider_openai=0 (db-checks.log).

web: an untrusted callback creates no session and no account - replay GET /testing/oai-redirect?code=not-a-real-code&state=<state copied from the start step> in a context holding the transaction… — cb-e4-nocookie.png

web: connecting with the real ChatGPT account shows the connected state - tap 'Sign in with ChatGPT', complete consent with the supplied account, return to /byok: the card shows the connected… — e5-card-2-after-connect.png

web: the disconnected state is clear and the page does not shift - sign in with the test account, open /byok, the OpenAI card shows the connect explanation and a single 'Sign in with ChatGPT' button… — e7-resolved-375.png

web: a declined permission is reported readably and offers Try again - with a signed-in session, navigate to /testing/oai-redirect?error=access_denied&state=stub: the person lands on /byok (not the… — e9-declined.png

web: one click starts a connection, not the sign-in Turnstile bounce - signed in with the test account, open /byok and tap Sign in with ChatGPT once; assert the browser reaches… — e10-after-click.png

web: disconnect is one click and returns the card to the disconnected state - with a connection present, tap 'Disconnect' once: the card shows the connect state again with no dialog, select count(*)… — e11-after-disconnect.png

web: an expired or failed connection is readable and offers one clear recovery - with a fixture connection row carrying is_enabled false, status 'error' and the message 'Your ChatGPT connection has… — e15-error-w1440-resolved.png

web: a failed disconnect is readable and retryable - with a fixture connection row, make the disconnect mutation fail (network off or a 500) and tap Disconnect once: the card shows We couldn't… — e16-disconnect-failed.png

web: the expired state keeps one recovery and one removal, and the page does not move - with a fixture connection row (is_enabled false, status error, message Your ChatGPT connection has expired. — r3-e6-1440-error.png

web: a failed token exchange comes back to the BYOK card - signed in, start the connect from /byok, then replay GET /testing/oai-redirect?code=not-a-real-code&state=<the state quoted from the start… — r3-e8-375-alert.png

E2E proof — log excerpts

[e1] web: an expired access token is refreshed transparently for a live connecti -> pass :: Connection created through the real connect flow against the verifier stub issuer (stub-issuer.log) because the worktree env holds placeholder OPENAI_CLIENT_ID/SECRET; set expires_at=1789606642 (=now-60) then one POST /api/openrouter/responses -> HTTP 200, after: expires_at=1789610306 > now, access_token CHANGED (e1-refresh.log).
[e2] web: an enabled connection serves an OpenAI model -> pass :: POST openai/gpt-5-nano -> 200 with a normal completion; nextjs log line 'Routing request to openai-chatgpt' (e2-gateway-nextjs.log) and stub upstream line 'UPSTREAM POST /v1/responses auth=present hdrNames=authorization,openai-on-behalf-of-token' (stub-issuer.log); usage row '2026-09-17 00:58:37.565+00|openai-chatgpt||gpt-5-nano|openai/gpt-5-nano|0|9|1|f' carries cost=0, no model charge (e2-usage.log).
[e3] web: a terminal refresh failure clears the connection and asks for a reconn -> pass :: Fixture row with refresh_token invalid_grant + stub issuer: POST -> 400 'Your ChatGPT connection has expired. Reconnect to continue. Reconnect your ChatGPT connection at http://localhost:6900/byok, or choose a different model.', stored payload hasAccessToken=false hasRefreshToken=false is_enabled=f, /byok card 'Needs reconnect Your ChatGPT connection has expired. Reconnect to continue. Reconnect with ChatGPT Disconnect', upstreamRequestsDuringRequest=0 microdollarBefore=6 microdollarAfter=6 (no other billing path).
/home/igor_kilocode_ai/.local/share/kwf/sections/owner-signin-with-chatgpt-dc3c/e2e-web/e1-refresh.log
e1 user=c1253531-a73b-4c0d-83d8-c60bd8bea7e4
<redacted>
e1 set expires_at=1789606642 (2026-09-17T00:57:22.000Z) = now-60
e1 POST status=200 elapsedMs=3777 body={"id":"resp_vf_f74722250cb1","object":"response","created_at":1789606706,"status":"completed","model":"gpt-5-nano","output":[{"type":"message","id":"msg_vf_7a4b9
e1 after expires_at=1789610306 (2026-09-17T01:58:26.000Z) now=1789606706
e1 access_token CHANGED before=vf-access-c8dd79… after=vf-access-404ca2…
e1 refresh_token CHANGED
e1 expires_at_in_future=true
/home/igor_kilocode_ai/.local/share/kwf/sections/owner-signin-with-chatgpt-dc3c/e2e-web/stub-issuer.log
[stub] 2026-09-17T00:55:56.835Z POST /__identity
[stub] 2026-09-17T00:55:56.836Z identity set {"email":"vf-verify@example.com","sub":"vf-verify-sub","name":"Stub Person","fail":null}
[stub] 2026-09-17T00:55:56.853Z GET /__identity
[stub] 2026-09-17T00:55:56.854Z identity set {"email":"vf-verify@example.com","sub":"vf-verify-sub","name":"Stub Person","fail":null}
[stub] 2026-09-17T00:56:44.543Z GET /.well-known/openid-configuration
[stub] 2026-09-17T00:56:44.582Z GET /api/accounts/authorize ?client_id=vf-stub-openai-client&scope=openid%20profile%20email%20offline_access%20resource.invoke%20chatpass.enable.request&response_type=c
[stub] 2026-09-17T00:56:44.830Z GET /.well-known/openid-configuration
[stub] 2026-09-17T00:56:44.845Z POST /api/accounts/oauth/token
[stub] 2026-09-17T00:56:44.845Z token request {"grant_type":"authorization_code","has_code":true,"known_code":true}
[stub] 2026-09-17T00:56:44.855Z GET /.well-known/jwks.json
[stub] 2026-09-17T00:57:19.119Z POST /api/accounts/oauth/token
[stub] 2026-09-17T00:57:19.119Z refresh request {"known":true}
[stub] 2026-09-17T00:57:19.120Z refresh ok {"newAccessToken":"vf-access-c8dd791beb31ffc43a6e7830"}
[stub] 2026-09-17T00:57:32.131Z UPSTREAM POST /v1/responses auth=MISSING hdrNames=
[stub] 2026-09-17T00:57:32.131Z UPSTREAM body model=gpt-5-nano keys=model,input
[stub] 2026-09-17T00:58:26.350Z POST /api/accounts/oauth/token
[stub] 2026-09-17T00:58:26.351Z refresh request {"known":true}
[stub] 2026-09-17T00:58:26.351Z refresh ok {"newAccessToken":"vf-access-404ca2b5e15a9a3cdebad233"}
[stub] 2026-09-17T00:58:26.392Z UPSTREAM POST /v1/responses auth=present hdrNames=authorization,openai-on-behalf-of-token
[stub] 2026-09-17T00:58:26.392Z UPSTREAM body model=gpt-5-nano keys=model,input,stream,safety_identifier,user,store
/home/igor_kilocode_ai/.local/share/kwf/sections/owner-signin-with-chatgpt-dc3c/e2e-web/e2-gateway.log
e2 user=c1253531-a73b-4c0d-83d8-c60bd8bea7e4 microdollarBefore=5 microdollarAllBefore=8
e2 POST status=200 body={"id":"resp_vf_7cb78e6ef046","object":"response","created_at":1789606717,"status":"completed","model":"gpt-5-nano","output":[{"type":"message","id":"msg_vf_a6c66c783796","statu
e2 microdollarAfter=6 microdollarAllAfter=9 newRows=1
e2 nextjsLogLines=["Routing request to openai-chatgpt"]
/home/igor_kilocode_ai/.local/share/kwf/sections/owner-signin-with-chatgpt-dc3c/e2e-web/e2-gateway-nextjs.log
30923:Routing request to openai-chatgpt
30928:Routing request to openai-chatgpt
/home/igor_kilocode_ai/.local/share/kwf/sections/owner-signin-with-chatgpt-dc3c/e2e-web/e2-usage.log
2026-09-17 00:58:37.565+00|openai-chatgpt||gpt-5-nano|openai/gpt-5-nano|0|9|1|f
2026-09-17 00:58:26.405+00|openai-chatgpt||gpt-5-nano|openai/gpt-5-nano|0|9|1|f
2026-09-17 00:57:27.167+00|openrouter|OpenAI|openai/gpt-5-nano|openai/gpt-5-nano|35|12|86|f
2026-09-16 21:41:43.941+00|openrouter|OpenAI|openai/gpt-5-nano|openai/gpt-5-nano|40|12|99|f
/home/igor_kilocode_ai/.local/share/kwf/sections/owner-signin-with-chatgpt-dc3c/e2e-web/e3-summary.log
e3 POST status=400 body={"error":"Your ChatGPT connection has expired. Reconnect to continue. Reconnect your ChatGPT connection at http://localhost:6900/byok, or choose a different model.","error_type
e3 storedAfter status=error message="undefined" is_enabled=f hasAccessToken=false hasRefreshToken=false
e3 storedKeys=access_token,expires_at,scope,token_type,issuer,client_id,subject,email,connected_at,status,error_message,error_at
e3 upstreamRequestsDuringRequest=0 microdollarBefore=6 microdollarAfter=6
BODY_AROUND_OPENAI  AI output per dollar with Kilo Pass Get Kilo Pass VV VF Verify vf-verify@example.com Toggle Sidebar Bring Your Own Key OpenAI (ChatGPT subscription) Needs reconnect Your ChatGPT co
Owner request for the stack

Surface: web.

Build two things.

  1. "Sign in with ChatGPT"
  • Add it as a login and signup option beside the existing methods.
  • It must bind to an existing account correctly: the same verified person signs
    in to their existing account, never a duplicate account.
  • A person with no account gets a new account.
  • Use the verified OpenAI issuer plus the sub claim as the stable external
    identity. Validate the ID token against the issuer JWKS, the audience, the
    expiry and the nonce before you trust any claim.
  1. "OpenAI BYOK"
  • Add a new OpenAI entry to bring your own key (BYOK).
  • The person connects with a "Sign in with ChatGPT" call-to-action button, not a
    pasted API key.
  • When the connection is enabled, OpenAI models use it.
  • The UX must be excellent: a clear connected and disconnected state, one click
    to connect, one click to disconnect, and a readable message when the
    connection fails or expires.

OAuth client, production:

  • The registered callback path is exactly /testing/oai-redirect
  • The client id is oaiapp_Abz1xcqSQAvvIwtxyemZbXBJ
  • Read the client id from the environment as OPENAI_CLIENT_ID. Do not write it
    into the source.
  • Read the client secret from the server-side secret manager as
    OPENAI_CLIENT_SECRET. Never print it, never commit it, never write it to a log
    line, a screenshot or a pull request.
  • Generate PKCE values and exchange tokens on the backend only. Never in browser
    JavaScript.

Reference documentation:

  • Read ~/Projects/.kilo_workflow/ops/refs/sign-in-with-chatgpt.md
  • Start with the "On your website" section. It holds the discovery URL, the
    Authorization Code plus PKCE flow, ID-token validation and account linking.
  • The document is confidential. Do not quote it in the pull request.

If the live flow needs a real OpenAI account to prove, block and name the need.
Igor supplies the account.

[e20] ux-check: declining over a Needs reconnect state keeps the reconnect context — with a stored needs-reconnect row, the declined consent returns to /byok where the card still shows 'Needs reconnect' + the expired sentence + 'Reconnect with ChatGPT' and 'Disconnect'.

[e20] ux-check: declining over a Needs reconnect state keeps the reconnect context — e20-decline-reconnect-samehost.png

[e19] ux-check: exactly one CTA and a visible in-progress state — /byok with no connection has exactly 1 'Sign in with ChatGPT' button and, on click, its label becomes 'Redirecting to ChatGPT...' with disabled=true for the whole observation window (it never becomes clickable again).

[e19] ux-check: exactly one CTA and a visible in-progress state — e19-in-progress.png

[e21] ux-check: no two entries are both labelled exactly 'OpenAI' — the subscription card is labelled 'OpenAI (ChatGPT subscription)' and the page contains 0 elements whose text is exactly 'OpenAI'; the paste-key dialog's provider list renders only on open, so its item label was not observed.

[e21] ux-check: no two entries are both labelled exactly 'OpenAI' — e21-byok-labels.png

[e15] ux-check: sign-in and sign-up both show 'Sign in with ChatGPT' and it reaches the consent screen — probe-e15.log: 'e15 signup clicked url= https://auth.openai.com/api/accounts/authorize?...response_type=code...' and probe-auth.log e1/p1 both show hasChatGPT=true with the OAuth group (screenshots shots/e15-signup-after-click.png, shots/e1-signin.png, shots/p1-signup.png).

[e15] ux-check: sign-in and sign-up both show 'Sign in with ChatGPT' and it reaches the consent screen — e15-signup-after-click.png

[p8] ux-check: no two entries labelled exactly 'OpenAI'; the subscription card and the paste-key entry are distinguishable — The p8 probe printed 'p8 exactOpenaiLabels=0 cardTitleCount=1 cardText="OpenAI (ChatGPT subscription) Connect with your ChatGPT subscription to use OpenAI models in Kilo. No API key needed. Sign in with ChatGPT"'; no exact 'OpenAI' label exists and the card is qualified (screenshot shots/p8-labels.png).

[p8] ux-check: no two entries labelled exactly 'OpenAI'; the subscription card and the paste-key entry are distinguishable — p8-labels.png

[e7] web: the disconnected state is clear and the page does not shift - sign in with the test account, open /byok, the OpenAI card shows the connect explanation and a single 'Sign in with ChatGPT' button… — card-e7.log: 'e7 w=375 loadingY=396 resolvedY=396 delta=0 ... pageChatGptButtons=1' and 'e7 w=1440 loadingY=358 resolvedY=358 delta=0 ... pageChatGptButtons=1', card text 'Connect with your ChatGPT subscription to use OpenAI models in Kilo. No API key needed.' (screenshots shots/e7-loading-375.png, shots/e7-resolved-375.png).

[e7] web: the disconnected state is clear and the page does not shift - sign in with the test account, open /byok, the OpenAI card shows the connect explanation and a single 'Sign in with ChatGPT' button… — e7-loading-375.png

[e15] ux-check: sign-in and sign-up both show 'Sign in with ChatGPT' and it reaches the consent screen

[e15] ux-check: sign-in and sign-up both show 'Sign in with ChatGPT' and it reaches the consent screen — p1-signup.png

[e10] web: one click starts a connection, not the sign-in Turnstile bounce - signed in with the test account, open /byok and tap Sign in with ChatGPT once; assert the browser reaches… — probe-e10.log: signed-in /byok click lands on 'https://auth.openai.com/api/accounts/authorize?...scope=openid profile email offline_access resource.invoke chatpass.enable.request&response_type=code...' and 'e10 finalUrl= https://auth.openai.com/... turnstile= false'; screenshots shots/e10-byok-before.png, shots/e10-after-click.png.

[e10] web: one click starts a connection, not the sign-in Turnstile bounce - signed in with the test account, open /byok and tap Sign in with ChatGPT once; assert the browser reaches… — e10-byok-before.png

[e10] ux-check: the 'BYOK API Keys' offset does not change across card states at 375px and 1440px — vfe-offset.log: 'e10 w=375 disconnected loadingY=396 resolvedY=396 delta=0', 'e10 w=375 connected ... delta=0', 'e10 w=1440 disconnected loadingY=358 resolvedY=358 delta=0', 'e10 w=1440 connected ... delta=0' with 3 skeletons during loading (shots/e10-disconnected-375-loading.png, shots/e10-connected-375.png, shots/e10-disconnected-1440.png).

[e10] ux-check: the 'BYOK API Keys' offset does not change across card states at 375px and 1440px — e10-disconnected-375-loading.png

[e10] ux-check: the 'BYOK API Keys' offset does not change across card states at 375px and 1440px

[e10] ux-check: the 'BYOK API Keys' offset does not change across card states at 375px and 1440px — e10-disconnected-1440.png

[e10] ux-check: the 'BYOK API Keys' offset does not change across card states at 375px and 1440px

[e10] ux-check: the 'BYOK API Keys' offset does not change across card states at 375px and 1440px — e10-connected-375.png

[p6] web: disconnect is one click and returns the card to the disconnected state - with a connection present, tap 'Disconnect' once: the card shows the connect state again with no dialog, select count(*)… — Fixture connected row (placeholder tokens, is_enabled=true), one click on Disconnect: r3-disconnect.log 'r3p6 before="OpenAI (ChatGPT subscription) Connected Connected as vf-verify@example.com Connected on 9/16/2026 Disconnect" beforeY=362 after="OpenAI (ChatGPT subscription) Connect with your ChatGPT subscription to use OpenAI models in Kilo. No API key needed. Sign in with ChatGPT" afterButtons=["Sign in with ChatGPT"] afterY=362 delta=0 rows=0' - no dialog, the row is gone and nothing below the card moved; captures shots/r3-p6-before.png and shots/r3-p6-after.png.

[p6] web: disconnect is one click and returns the card to the disconnected state - with a connection present, tap 'Disconnect' once: the card shows the connect state again with no dialog, select count(*)… — r3-p6-after.png

[p6] web: disconnect is one click and returns the card to the disconnected state - with a connection present, tap 'Disconnect' once: the card shows the connect state again with no dialog, select count(*)…

[p6] web: disconnect is one click and returns the card to the disconnected state - with a connection present, tap 'Disconnect' once: the card shows the connect state again with no dialog, select count(*)… — r3-p6-before.png

[e6] web: an expired or failed connection is readable and offers one clear recovery - with a fixture connection row carrying is_enabled false, status 'error' and the message 'Your ChatGPT connection has… — Fixture row mirroring the real payload (is_enabled=false, status error, error_message, dumped in e2e-web/r3-fixture.log, encrypted with the app BYOK_ENCRYPTION_KEY): r3-states.log 'r3 w=375 discY=423 errLoadingY=423 errY=423 errDelta=0 errVsDisc=0 connY=423 connVsDisc=0 skeletons=3', 'errCard="OpenAI (ChatGPT subscription) Needs reconnect Your ChatGPT connection has expired. Reconnect to continue.', 'errButtons=["Reconnect with ChatGPT","Disconnect"]' and 'sentence=true hasReconnect=true hasDisconnect=true sameAuthorizationAsConnect=true'; captures shots/r3-e6-375-loading.png and…

[e6] web: an expired or failed connection is readable and offers one clear recovery - with a fixture connection row carrying is_enabled false, status 'error' and the message 'Your ChatGPT connection has… — r3-e6-375-error.png

[p7] web: a declined permission is reported readably and offers Try again - with a signed-in session, navigate to /testing/oai-redirect?error=access_denied&state=stub: the person lands on /byok (not the… — r3-declined.log 'noRowCard="OpenAI (ChatGPT subscription) ChatGPT was not connected. Try again. Try again" noRowButtons=["Try again"] noRowY=423 noRowBodyHasMessage=true' and 'noRowY=362' at 1440, reached by arriving at /byok with openai_error=access_denied; capture shots/r3-p7-declined-norow-375.png.

[p7] web: a declined permission is reported readably and offers Try again - with a signed-in session, navigate to /testing/oai-redirect?error=access_denied&state=stub: the person lands on /byok (not the… — r3-p7-declined-norow-375.png

[p11] ux-check: declining consent on a stored needs-reconnect connection keeps the reconnect context and Disconnect — r3-declined.log 'storedBefore="OpenAI (ChatGPT subscription) Needs reconnect Your ChatGPT connection has expired. Reconnect to continue. Reconnect with ChatGPT Disconnect" beforeY=423 afterDecline="OpenAI (ChatGPT subscription) Needs reconnect ChatGPT was not connected. Try again. Reconnect with ChatGPT Disconnect"', 'declineDelta=0', 'rowsAfterDecline=1' and 'afterY=362' at 1440 - the card keeps the reconnect context and Disconnect, not only Try again; capture shots/r3-p11-declined-stored-375.png.

[p11] ux-check: declining consent on a stored needs-reconnect connection keeps the reconnect context and Disconnect — r3-p11-declined-stored-375.png

[p13] ux-check: the 'BYOK API Keys' offset does not change across card states at 375px and 1440px — r3-states.log 'connY=423 connVsDisc=0 skeletons=3' at 375px and 'r3 w=1440 discY=362' with the same run's errDelta=0/connVsDisc=0 - the loading skeleton, disconnected, expired and connected states all keep the heading at one offset, against a 23px shift before the fix; captures shots/r3-e6-375-loading.png, shots/r3-e6-375-error.png, shots/r3-e6-1440-loading.png, shots/r3-e6-1440-error.png, shots/r3-p13-connected-375.png, shots/r3-p5-disconnected-375.png.

[p13] ux-check: the 'BYOK API Keys' offset does not change across card states at 375px and 1440px — r3-p13-connected-375.png

[p12] ux-check: no two distinct entries on /byok are both labelled exactly 'OpenAI' — r3-labels.log 'r3p12 exactOpenaiLabels=0 cardTitleCount=1' - the ChatGPT-subscription card is the only OpenAI entry and no unqualified 'OpenAI' label exists on the page; capture shots/r3-p12-labels.png.

[p12] ux-check: no two distinct entries on /byok are both labelled exactly 'OpenAI' — r3-p12-labels.png

[p13] ux-check: the 'BYOK API Keys' offset does not change across card states at 375px and 1440px

[p13] ux-check: the 'BYOK API Keys' offset does not change across card states at 375px and 1440px — r3-e6-1440-loading.png

[e6] web: an expired or failed connection is readable and offers one clear recovery - with a fixture connection row carrying is_enabled false, status 'error' and the message 'Your ChatGPT connection has…

[e6] web: an expired or failed connection is readable and offers one clear recovery - with a fixture connection row carrying is_enabled false, status 'error' and the message 'Your ChatGPT connection has… — r3-e6-375-loading.png

[p9] web: a failed disconnect is readable and retryable - with a fixture connection row, make the disconnect mutation fail (network off or a 500) and tap Disconnect once: the card shows We couldn't… — Fault injected as a stubbed 500 on the disconnect mutation (the pack's e2e index lists no fault hook or test id for this surface, so the failing response is a stub): r3-disconnect-fail.log 'r3p9 before="OpenAI (ChatGPT subscription) Connected Connected as vf-verify@example.com Connected on 9/16/2026 Disconnect" beforeY=362 after="OpenAI (ChatGPT subscription) Connected We couldn't disconnect ChatGPT. Try again. Disconnect" afterButtons=["Disconnect"] afterY=362 delta=0 rows=1 blockedPosts=["openAiChatGpt.disconnect?batch=1"]' - readable failure, Disconnect kept, row intact and nothing below…

[p9] web: a failed disconnect is readable and retryable - with a fixture connection row, make the disconnect mutation fail (network off or a 500) and tap Disconnect once: the card shows We couldn't… — r3-p9-failed-disconnect.png

[p10] ux-check: one 'Sign in with ChatGPT' button with a visible in-progress state — r3-connect.log 'r3p10 pageChatGptButtons=1 cardButtons=["Sign in with ChatGPT"]' shows exactly one connect control with no connection, and the same click issues the authorization and leaves the page ('turnstileBounce=false'), so the control cannot be clicked twice before the browser leaves; the in-progress rendering itself is a visual claim and the pending navigation destroys the page context before any DOM read, which is left to the visual reviewer (captures shots/r3-p10-in-progress.png); the diff does not touch the button's click handler.

[p10] ux-check: one 'Sign in with ChatGPT' button with a visible in-progress state — r3-p10-in-progress.png

[p5] web: the disconnected state is clear and the page does not shift - sign in with the test account, open /byok, the OpenAI card shows the connect explanation and a single 'Sign in with ChatGPT' button… — r3-states.log 'discCard="OpenAI (ChatGPT subscription) Connect with your ChatGPT subscription to use OpenAI models in Kilo. No API key needed. Sign in with ChatGPT" discButtons=["Sign in with ChatGPT"]' with 'discY=423' equal to the held-skeleton offset measured in the same run, and the 1440 pair at 362 - one connect control, and the key-list heading is at the same offset before and after the card resolves; capture shots/r3-p5-disconnected-375.png.

[p5] web: the disconnected state is clear and the page does not shift - sign in with the test account, open /byok, the OpenAI card shows the connect explanation and a single 'Sign in with ChatGPT' button… — r3-p5-disconnected-375.png

[e6] web: the expired state keeps one recovery and one removal, and the page does not move - with a fixture connection row (is_enabled false, status error, message Your ChatGPT connection has expired. — Fixture row mirroring the real payload (is_enabled=false, status error, error_message, encrypted with the app BYOK_ENCRYPTION_KEY) inserted by the verifier driver: e6-375.log 'FIXTURE inserted is_enabled=false status=error rows=1', 'RESULT w=375 loadingHeadingY=423 resolvedHeadingY=423 delta=0', 'ASSERTS sentence=true hasReconnect=true hasDisconnect=true' and CARD carries 'Your ChatGPT connection has expired. Reconnect to continue.' with '"Reconnect with ChatGPT","Disconnect"'; e6-1440.log 'RESULT w=1440 loadingHeadingY=362 resolvedHeadingY=362 delta=0' - the 'BYOK API Keys' heading keeps one…

[e6] web: the expired state keeps one recovery and one removal, and the page does not move - with a fixture connection row (is_enabled false, status error, message Your ChatGPT connection has expired. — e6-375-resolved.png

[e6] web: the expired state keeps one recovery and one removal, and the page does not move - with a fixture connection row (is_enabled false, status error, message Your ChatGPT connection has expired.

[e6] web: the expired state keeps one recovery and one removal, and the page does not move - with a fixture connection row (is_enabled false, status error, message Your ChatGPT connection has expired. — e6-1440-resolved.png

[e5] web: connecting with the real ChatGPT account shows the connected state - tap 'Sign in with ChatGPT', complete consent with the supplied account, return to /byok: the card shows the connected… — The card's own connect control was exercised after deleting the session's row: e5-card.log 'ROW_DELETED' follows 'CARD_BEFORE' with '"card":"OpenAI (ChatGPT subscription) Connect with your ChatGPT subscription to use OpenAI models in Kilo. No API key needed. Sign in with ChatGPT' and '"chatgptButtons":["Sign in with ChatGPT"]', and 'CARD_AFTER' shows '"card":"OpenAI (ChatGPT subscription) Connected Connected as vf-e5b-1789603892@example.com Connected on 9/17/2026 Disconnect' with '"chatgptButtons":[]'; e5-post-db.log records…

[e5] web: connecting with the real ChatGPT account shows the connected state - tap 'Sign in with ChatGPT', complete consent with the supplied account, return to /byok: the card shows the connected… — e5-card-1-disconnected.png

Owner manual verification

Owner verification is pending; these checks did not pass automatically.

  • live ChatGPT connect / reconnect / disconnect with the account Igor supplies: complete the OpenAI consent on https://auth.openai.com, return to /byok and confirm Connected as plus the connected date, force the stored token to expire and reconnect, then disconnect. Needs OPENAI_CLIENT_ID/OPENAI_CLIENT_SECRET in the worktree env and the supplied ChatGPT account; skipped from automatic runs and listed as pending in the PR. platform:web

PR stack (merge bottom to top)

Comment thread apps/web/src/lib/user/server.ts
Comment thread apps/web/src/lib/ai-gateway/openai-chatgpt/refresh.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 3 Issues Found | Recommendation: Address before merge

Executive Summary

A previously-fixed path regressed: a prefilled ?email= or returning-user address no longer reaches the ChatGPT flag hook, so the sign-in option can be filtered out, and the new served-model lookup ignores the upstream override and never caches failures.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/components/auth/SignInForm.tsx 68 Prefilled ?email= / returning-hint addresses never reach useChatGptSignInAccess, so the ChatGPT option is filtered out at provider selection

SUGGESTION

File Line Issue
apps/web/src/lib/ai-gateway/openai-chatgpt/served-models.ts 12 Hardcoded api.openai.com/v1/models ignores the documented OPENAI_CHATGPT_API_URL override
apps/web/src/lib/ai-gateway/openai-chatgpt/served-models.ts 56 Failed model-list fetches are not cached, so each request re-issues the lookup (up to 2s) during an outage
Files Reviewed (9 files)
  • apps/web/src/components/auth/SignInForm.tsx - 1 issue
  • apps/web/src/components/auth/SignInForm.signInOptions.test.ts - no issues
  • apps/web/src/hooks/useChatGptSignInAccess.ts - no issues
  • apps/web/src/hooks/useChatGptSignInAccess.test.ts - no issues
  • apps/web/src/lib/ai-gateway/openai-chatgpt/routing.ts - no issues
  • apps/web/src/lib/ai-gateway/openai-chatgpt/routing.test.ts - no issues
  • apps/web/src/lib/ai-gateway/openai-chatgpt/served-models.ts - 2 issues
  • apps/web/src/lib/ai-gateway/openai-chatgpt/served-models.test.ts - no issues
  • apps/web/src/lib/ai-gateway/providers/get-provider.local-fake.test.ts - no issues (previous env-var WARNING resolved)

Resolved since the previous review: get-provider.local-fake.test.ts now overrides OPENAI_CHATGPT_API_KEY, so the delegated-route test passes again.

Fix these issues in Kilo Cloud

Previous Review Summaries (8 snapshots, latest commit f5b6107)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit f5b6107)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

Renaming the delegated partner-key env var to OPENAI_CHATGPT_API_KEY left get-provider.local-fake.test.ts asserting on OPENAI_API_KEY, so its delegated-route test now fails.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/ai-gateway/openai-chatgpt/routing.ts 24 Env rename leaves apps/web/src/lib/ai-gateway/providers/get-provider.local-fake.test.ts:217 setting OPENAI_API_KEY, so an enabled connection beats a Vercel openai BYOK row for an eligible responses request now falls through and fails
Files Reviewed (3 files)
  • ENVIRONMENT.md - no issues
  • apps/web/src/lib/ai-gateway/openai-chatgpt/routing.ts - 1 issue
  • apps/web/src/lib/ai-gateway/openai-chatgpt/routing.test.ts - no issues

Fix these issues in Kilo Cloud

Previous review (commit bae99ce)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental review at bae99ce19 found no new issues; the traceability-metadata addition enforces OpenAI's 16-entry limit while keeping the required keys, and the prior routing findings remain resolved.

Files Reviewed (2 files)
  • apps/web/src/lib/ai-gateway/openai-chatgpt/routing.ts - no issues; withTraceabilityMetadata merges caller metadata with the required traceability keys, drops caller entries when the entry limit is exceeded, and keeps the required keys; delete body.user leaves safety_identifier intact.
  • apps/web/src/lib/ai-gateway/openai-chatgpt/routing.test.ts - no issues; tests cover the required keys, the session-driven activity id, and the entry-limit trim.

Previous review (commit 9f2b643)

Status: No Issues Found | Recommendation: Merge

Incremental review at 9f2b64301: the prior routing-mock warning is resolved. No new issues found in the changed code.

Files Reviewed (2 files)
  • apps/web/src/lib/ai-gateway/providers/get-provider.local-fake.test.ts - previous mock warning resolved; mock now returns { connection, isEnabled: true } matching isOpenAiChatGptEligible
  • apps/web/src/lib/user/server.ts - no issues; exchangeBody: { resource } correctly restores the RFC 8707 resource on the OpenAI code exchange

Previous review (commit d0879e6)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/ai-gateway/openai-chatgpt/routing.ts 83 isOpenAiChatGptEligible now calls getOpenAiChatGptStoredConnection, which the unchanged mock factory in get-provider.local-fake.test.ts does not provide, so the four responses-based ChatGPT routing tests throw TypeError
Files Reviewed (11 files)
  • apps/web/src/app/(app)/byok/page.tsx - no issues (prior layout-shift suggestion resolved)
  • apps/web/src/app/(app)/byok/page.test.ts - no issues
  • apps/web/src/components/auth/SignInForm.signInOptions.test.ts - no issues (prior mock-argument suggestion resolved)
  • apps/web/src/hooks/useChatGptSignInAccess.ts - no issues (both prior warnings resolved)
  • apps/web/src/hooks/useChatGptSignInAccess.test.ts - no issues
  • apps/web/src/lib/ai-gateway/openai-chatgpt/routing.ts - 1 issue
  • apps/web/src/lib/ai-gateway/openai-chatgpt/routing.test.ts - no issues
  • apps/web/src/lib/ai-gateway/openai-chatgpt/store.ts - no issues
  • apps/web/src/lib/ai-gateway/openai-chatgpt/store.test.ts - no issues
  • apps/web/src/lib/ai-gateway/openai-chatgpt/refresh.ts - no issues
  • apps/web/src/lib/ai-gateway/openai-chatgpt/refresh.test.ts - no issues

Notes

  • Prior findings re-verified as fixed at HEAD d0879e6a5: the hook flips a readiness state from onFeatureFlags and debounces the typed email by EMAIL_SETTLE_MS; /byok renders OpenAiChatGptCardView status={undefined} while the flag is undefined; the sign-in-options test now asserts the hook receives flow.email.
  • apps/web/src/lib/auth/openai/config.ts is unchanged in this revision; the author replied that the OpenAI client was re-registered against /auth/openai/callback for both environments, so that warning is treated as addressed outside the code.
  • Memory-leak check (requested): no new leaks. The revision adds no timers, intervals, addEventListener, or subscriptions that outlive their cleanup; the hook clears its setTimeout and unsubscribes both onFeatureFlags listeners.
  • No markdown files are changed in this revision, so the image-syntax rule does not apply.

Fix these issues in Kilo Cloud

Previous review (commit d20e6a0)

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/hooks/useChatGptSignInAccess.ts 22 The effect bails while PostHog is still loading and never re-runs, so the flag is never evaluated for a prefilled-email landing
apps/web/src/hooks/useChatGptSignInAccess.ts 35 Feature flags are reloaded on every keystroke while the visitor types their email
apps/web/src/lib/auth/openai/config.ts 27 The registered OAuth callback path changed (was /testing/oai-redirect); confirm the OpenAI client registration matches

SUGGESTION

File Line Issue
apps/web/src/app/(app)/byok/page.tsx 17 The card is unmounted while the flag resolves, so the key list shifts when it appears
apps/web/src/components/auth/SignInForm.signInOptions.test.ts 23 The hook mock discards the email argument, so the email wiring is untested
Files Reviewed (6 files)

Changed in revision d20e6a06 (format/lint follow-up):

  • apps/web/src/components/auth/SignInForm.tsx - Prettier re-wrap of the withoutChatGptWhenUnavailable(...) call; no behavior change
  • apps/web/src/lib/auth/provider-metadata.test.ts - static import plus icon-identity assertion replace the require and global React shim; the automatic JSX runtime means no mock ordering is required, so no issue

Carried forward and re-verified at HEAD d20e6a06:

  • apps/web/src/hooks/useChatGptSignInAccess.ts - 2 issues
  • apps/web/src/lib/auth/openai/config.ts - 1 issue
  • apps/web/src/app/(app)/byok/page.tsx - 1 issue
  • apps/web/src/components/auth/SignInForm.signInOptions.test.ts - 1 issue

Notes

  • No new findings in revision d20e6a06; the five findings above are unchanged from the prior revision and remain open.
  • Memory-leak check (requested): no new leaks. This revision adds no timers, intervals, addEventListener, or AbortController.
  • No markdown files are changed in this revision, so the image-syntax rule does not apply.
  • A human review left additional inline suggestions on apps/web/src/lib/ai-gateway/openai-chatgpt/store.ts, apps/web/src/lib/ai-gateway/openai-chatgpt/refresh.ts, and apps/web/src/lib/ai-gateway/providers/get-provider.ts; they are already visible on the PR and are not repeated here.

Fix these issues in Kilo Cloud

Previous review (commit 0cd12eb)

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/hooks/useChatGptSignInAccess.ts 22 The effect bails while PostHog is still loading and never re-runs, so the flag is never evaluated for a prefilled-email landing
apps/web/src/hooks/useChatGptSignInAccess.ts 35 Feature flags are reloaded on every keystroke while the visitor types their email
apps/web/src/lib/auth/openai/config.ts 27 The registered OAuth callback path changed (was /testing/oai-redirect); confirm the OpenAI client registration matches

SUGGESTION

File Line Issue
apps/web/src/app/(app)/byok/page.tsx 17 The card is unmounted while the flag resolves, so the key list shifts when it appears
apps/web/src/components/auth/SignInForm.signInOptions.test.ts 23 The hook mock discards the email argument, so the email wiring is untested
Files Reviewed (16 files)
  • apps/web/src/hooks/useChatGptSignInAccess.ts - 2 issues
  • apps/web/src/lib/auth/openai/config.ts - 1 issue
  • apps/web/src/app/(app)/byok/page.tsx - 1 issue
  • apps/web/src/components/auth/SignInForm.signInOptions.test.ts - 1 issue
  • apps/web/src/components/auth/SignInForm.tsx - verified; provider filter and returning-user fallback are correct
  • apps/web/src/components/auth/OpenAILogo.tsx - aria-hidden plus <title> matches the sibling brand logos; sizing preserved at 20px (size-5)
  • apps/web/src/app/auth/openai/callback/route.ts - moved; no behavior change
  • apps/web/src/app/auth/openai/callback/route.test.ts - path strings only
  • apps/web/src/lib/auth/openai/config.test.ts - path assertion only
  • apps/web/src/lib/auth/provider-metadata.tsx - icon swap only
  • apps/web/src/lib/auth/provider-metadata.test.ts - no issues
  • apps/web/src/lib/user/server.ts - comment only
  • apps/web/src/lib/user/server.test.ts - path assertion only
  • apps/web/src/app/(app)/byok/page.test.ts - the loading case does not add discriminating coverage over the flag-off case
  • apps/web/src/lib/auth/openai/access.ts - constant only
  • apps/web/src/components/organizations/byok/OpenAiChatGptCard.tsx - comment only

Notes

Memory-leak check (requested): no new leaks. posthog.onFeatureFlags is unsubscribed on cleanup, and no timers, intervals, addEventListener, or AbortController were added by this commit. One un-gated entry point exists by design: the openai NextAuth provider stays registered and Connected Accounts still offers linking, so the flag is a UI rollout gate, not an authorization boundary (no server-side flag check exists). No markdown files are touched by this commit.

Fix these issues in Kilo Cloud

Previous review (commit 28ff13c)

Status: No New Issues Found | Recommendation: Merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0

Both findings from the previous review are resolved in the current revision.

Previous findings, re-verified on changed code
  • apps/web/src/lib/user/server.ts (identity-only ChatGPT sign-in stored as the delegated BYOK credential) — Resolved. persistOpenAiChatGptConnection now returns early unless isOpenAiTokenSharingGrant(account) holds, and the new helper requires both resource.invoke and offline_access (falling back to a refresh token only when the token response omits scope). A plain sign-in requests OPENAI_IDENTITY_SCOPE only (server.ts:719), so it stores nothing and can no longer be preferred for eligible requests or overwrite a working connection.
  • apps/web/src/lib/ai-gateway/openai-chatgpt/refresh.ts (retry backoff under the row lock) — Resolved. A single locked attempt returns a retry decision, and the caller backs off and re-reads the row in a new transaction, adopting a sibling's rotated token or releasing the pool connection between attempts.
Files Reviewed in this revision (7 files)
  • apps/web/src/lib/auth/openai/scopes.ts
  • apps/web/src/lib/auth/openai/config.ts
  • apps/web/src/lib/auth/openai/config.test.ts
  • apps/web/src/lib/user/server.ts
  • apps/web/src/lib/user/server.test.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/refresh.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/refresh.test.ts

Previous review (commit 6596424)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 0
SUGGESTION 1

Fix these issues in Kilo Cloud

Issue Details (click to expand)

CRITICAL

File Line Issue
apps/web/src/lib/user/server.ts 1157 A plain ChatGPT sign-in stores an identity-scoped token (no offline_access/resource.invoke) as the delegated BYOK credential; eligibility only checks status === 'connected', so it is preferred for eligible responses requests and the upsert can overwrite a working connection.

SUGGESTION

File Line Issue
apps/web/src/lib/ai-gateway/openai-chatgpt/refresh.ts 236 Refresh retries and their backoff sleep run while the SELECT ... FOR UPDATE row lock is held.
Files Reviewed (44 files)
  • ENVIRONMENT.md
  • apps/web/src/app/(app)/byok/page.tsx
  • apps/web/src/app/api/openrouter/[...path]/route.test.ts
  • apps/web/src/app/api/openrouter/[...path]/route.ts
  • apps/web/src/app/testing/oai-redirect/route.test.ts
  • apps/web/src/app/testing/oai-redirect/route.ts
  • apps/web/src/components/auth/SignInForm.signInOptions.test.ts
  • apps/web/src/components/auth/SignInForm.tsx
  • apps/web/src/components/auth/sign-in/AuthProviderButtons.tsx
  • apps/web/src/components/auth/sign-in/EmailInputForm.test.ts
  • apps/web/src/components/auth/sign-in/EmailInputForm.tsx
  • apps/web/src/components/organizations/byok/BYOKKeysManager.test.ts
  • apps/web/src/components/organizations/byok/BYOKKeysManager.tsx
  • apps/web/src/components/organizations/byok/OpenAiChatGptCard.test.ts
  • apps/web/src/components/organizations/byok/OpenAiChatGptCard.tsx
  • apps/web/src/lib/ai-gateway/llm-proxy-helpers.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/provider-id.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/refresh.test.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/refresh.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/routing.test.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/routing.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/status.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/store.test.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/store.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/types.ts
  • apps/web/src/lib/ai-gateway/providers/get-provider.local-fake.test.ts
  • apps/web/src/lib/ai-gateway/providers/get-provider.ts
  • apps/web/src/lib/ai-gateway/providers/types.ts
  • apps/web/src/lib/auth/constants.ts
  • apps/web/src/lib/auth/openai/config.test.ts
  • apps/web/src/lib/auth/openai/config.ts
  • apps/web/src/lib/auth/openai/scopes.ts
  • apps/web/src/lib/auth/provider-metadata.test.ts
  • apps/web/src/lib/auth/provider-metadata.tsx
  • apps/web/src/lib/config.server.ts
  • apps/web/src/lib/user/index.test.ts
  • apps/web/src/lib/user/index.ts
  • apps/web/src/lib/user/server.test.ts
  • apps/web/src/lib/user/server.ts
  • apps/web/src/routers/openai-chatgpt-router.test.ts
  • apps/web/src/routers/openai-chatgpt-router.ts
  • apps/web/src/routers/root-router.ts
  • packages/db/src/schema-types.ts
  • services/kilo-mcp/catalog.json

Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

@iscekic
iscekic marked this pull request as draft September 17, 2026 01:17
@iscekic
iscekic force-pushed the kwf/owner-signin-with-chatgpt-dc3c-l1 branch from d204d3e to 28ff13c Compare September 17, 2026 02:34
@iscekic
iscekic marked this pull request as ready for review September 17, 2026 03:47
@iscekic iscekic added the human-ready The PR is ready for human review. label Sep 17, 2026
@iscekic iscekic self-assigned this Sep 17, 2026
- Gate the ChatGPT sign-in option and the OpenAI BYOK card behind the
  PostHog flag `sign-in-with-chatgpt`, whose release condition holds the
  approved email domains.
- Evaluate the flag against the email a signed-out visitor types, so the
  sign-in page honors the same allow-list without a hardcoded list.
- Show the OpenAI logo beside the ChatGPT sign-in label.
- Serve the OAuth callback at /auth/openai/callback.
Comment thread apps/web/src/hooks/useChatGptSignInAccess.ts Outdated
Comment thread apps/web/src/hooks/useChatGptSignInAccess.ts
Comment thread apps/web/src/lib/auth/openai/config.ts
Comment thread apps/web/src/app/(app)/byok/page.tsx Outdated
Comment thread apps/web/src/components/auth/SignInForm.signInOptions.test.ts Outdated

@pandemicsyn pandemicsyn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgmt, i think you could probably resolve most of these in a follow up PR if you want.

Comment thread apps/web/src/lib/ai-gateway/openai-chatgpt/store.ts Outdated
Comment thread apps/web/src/lib/ai-gateway/openai-chatgpt/refresh.ts Outdated
Comment thread apps/web/src/hooks/useChatGptSignInAccess.ts Outdated
Comment thread apps/web/src/hooks/useChatGptSignInAccess.ts
Comment thread apps/web/src/lib/ai-gateway/providers/get-provider.ts
- Re-evaluate the ChatGPT access flag when PostHog finishes loading, and
  debounce the typed email so a multi-character edit issues one reload.
- Assert the sign-in form passes the typed email to the hook; add hook tests.
- Hold the BYOK card's reserved height while the flag resolves.
- Exclude a row disabled through the BYOK toggle from delegated routing.
- Write the terminal refresh failure inside the row lock so a concurrent
  reconnect is not erased.
Comment thread apps/web/src/lib/ai-gateway/openai-chatgpt/routing.ts
openid-client builds the code exchange from a fixed field set and drops
`resource`, so an authorization that requested a resource was redeemed
without one and OpenAI rejected it with `invalid_grant`. Pass the
resource back through `exchangeBody`.

Also update the delegated-connection store mock in
get-provider.local-fake.test.ts for the new enabled-flag read.
OpenAI requires `subscription_sharing_activity` and
`subscription_sharing_purpose` in the request metadata of every delegated
Responses call. Add them, take `subscription_sharing_activity_id` from the
gateway session when there is one, and keep caller metadata within OpenAI's
16-entry limit.

Also drop the OpenRouter-only `user` field before the request reaches
api.openai.com.
`OPENAI_API_KEY` belongs to another project and doubles as the Vercel AI
Gateway BYOK credential. Read the delegated partner key from
`OPENAI_CHATGPT_API_KEY` instead, so the two paths cannot inherit each
other's project, quota, or billing.
Comment thread apps/web/src/lib/ai-gateway/openai-chatgpt/routing.ts
@iscekic iscekic removed the human-ready The PR is ready for human review. label Sep 17, 2026
@iscekic
iscekic marked this pull request as draft September 17, 2026 22:18
Comment thread apps/web/src/lib/ai-gateway/openai-chatgpt/routing.ts
Comment thread apps/web/src/lib/ai-gateway/openai-chatgpt/routing.ts
`get-provider.local-fake.test.ts` now overrides `OPENAI_CHATGPT_API_KEY`,
which the renamed env var made necessary.

Remove the `user` deletion: api.openai.com accepts the field on
`/v1/responses`, so it was not the fix and is not needed.
@iscekic iscekic added the human-ready The PR is ready for human review. label Sep 17, 2026
The sign-in page evaluated the flag as the visitor typed, one flags
request per keystroke. Evaluate it from the address they submit instead,
so one submission costs one reload and the option is decided before the
sign-in options render.
The catalog can list an OpenAI model the plain API does not serve, such as
`openai/gpt-5.6-luna-pro`, which answers with a 404. Sending it to the
delegated upstream fails the request instead of leaving it on its existing
route.

Eligibility now reads the project's `GET /v1/models` list, cached for an
hour. A missing key or an unreadable list keeps the route, so a transient
problem never hides it.
@iscekic
iscekic marked this pull request as ready for review September 18, 2026 00:32
@iscekic
iscekic merged commit c3d66de into main Sep 18, 2026
58 checks passed
@iscekic
iscekic deleted the kwf/owner-signin-with-chatgpt-dc3c-l1 branch September 18, 2026 00:32
Comment thread apps/web/src/components/auth/SignInForm.tsx
Comment thread apps/web/src/lib/ai-gateway/openai-chatgpt/served-models.ts
Comment thread apps/web/src/lib/ai-gateway/openai-chatgpt/served-models.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants