Skip to content

Kiro: dead refresh token (400 invalid_request) never flags needsReauth — dashboard keeps "Logged in" with no re-auth button #6701

Closed

Description

@klvfn

Client or integration

OpenCodex dashboard

Area

Authentication and account pool

Summary

When a Kiro refresh token becomes irredeemable (AWS SSO OIDC no longer accepts it), the account is never flagged needsReauth. The dashboard keeps showing Kiro as Ready / Connected / Logged in with no Re-authenticate button, and every request fails 401 with the generic fallback message. I expected the provider card to flip to "Needs attention" with the Re-authenticate button (or at least the specific "Not logged in to kiro. Run: ocx login kiro" message), like the terminal refresh-error path already produces.

Likely root cause (read from the installed 2.78.0 package):

  • KIRO_TERMINAL_REFRESH_ERRORS in src/oauth/kiro.ts covers only invalid_grant, refresh_token_reused, revoked, revoked_token, refresh_token_revoked, access_denied, expired_token.
  • The dead credential gets a consistent HTTP 400 from oidc..amazonaws.com/token with not in that set.
    {"error":"invalid_request","error_description":"Invalid request","location":null,"reason":null}
    
  • terminal() in src/oauth/index.ts treats KiroTokenRefreshError as terminal only when a 400/401 carries one of the known oauthError values, so the account is never marked; the raw error then surfaces through publicOAuthAuthenticationErrorMessage() as the generic "OAuth authentication failed." text.

Evidence:

  • Stored credential expired at 2026-10-06T10:50:38Z; tested at 2026-10-07T03:34Z.

  • GET /api/oauth/status?provider=kiro still returns loggedIn: true with that expired expiresAt and no needsReauth flag.

  • Client error on every request: "unexpected status 401 Unauthorized: OAuth authentication failed. Check the OpenCodex account status and retry."

  • Replaying the exact refresh request the proxy sends (same registered clientId/clientSecret, same refreshToken) returns HTTP 400 invalid_request.

  • The stored refresh token is identical to the local kiro-cli session token, whose access token is also expired. Only a fresh browser login restores access; no refresh path can.

Reproduction

  1. Import/complete a Kiro login (kiro-cli device/Builder ID flow), then let both the access token and the refresh grant go stale (e.g. leave it idle for a day).
  2. Send any request routed to the kiro provider, e.g. POST /v1/responses with model: kiro/.
  3. Observe the 401 generic OAuth message; dashboard Providers -> Kiro still shows Connected / Logged in with no Re-authenticate button, and Current account usage shows "Failed to refresh quotas".
  4. Manually POST the same refresh payload (grantType: refresh_token, clientId, clientSecret, refreshToken) to https://oidc.<region>.amazonaws.com/token and observe 400 invalid_request.

Version

2.78.0

Operating system

Ubuntu 26.04.1 LTS

Provider and model

kiro / claude-opus-5.5 (any Kiro model; the failure happens at the auth/token-resolution stage)

Logs or error output

[opencodex] OAuth refresh started provider=kiro account=account-…55fe

That line repeats roughly 40 times over an hour (request failures and quota probes keep re-attempting refresh); the credential never rotates and the account is never flagged.

ocx logs --provider kiro --limit 3 --json (redacted row fields):

    {
      "status": 401,
      "durationMs": 40,
      "errorCode": "invalid_api_key",
      "failureStage": "headers-only",
      "failureCause": "credential-rejected",
      "resendPermission": "permitted-after-repair",
      "upstreamError": "OAuth authentication failed. Check the OpenCodex account status and retry."
    }

AWS token endpoint replay (same registered client + stored refresh token as the proxy uses):

    HTTP 400
    {"error":"invalid_request","error_description":"Invalid request","location":null,"reason":null}

Screenshots and supporting files

Dashboard screenshot showing Kiro "Connected / Logged in" plus "Failed to refresh quotas" while all Kiro requests 401; client screenshot of the generic 401 error.

Redacted configuration

{
  "providers": {
    "kiro": {
      "adapter": "kiro",
      "authMode": "oauth",
      "baseUrl": "https://runtime.us-east-1.kiro.dev",
      "defaultModel": "kiro-auto"
    }
  },
  "tokenGuardian": null
}

Note: tokenGuardian is unset (defaults off) and Kiro's default refresh policy is lazy-only, so nothing proactively probes. But even a manual request never sets needsReauth either, because the 400 invalid_request response is not classified as terminal. A UI-level fix would also work: /api/oauth/status already returns expiresAt, and still renders a green "Logged in" for tokens that expired a day+ ago.

Suggested fix (either or both): treat the Kiro SSO 400 invalid_request refresh rejection as terminal (or map it to the verify_account reauth reason), and/or render token-expired state from expiresAt in the provider overview instead of a permanent healthy "Logged in".

Redacted configuration

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. added
    bugSomething isn't working
    on Oct 7, 2026
  2. added
    account-poolOAuth, credentials, Codex pool, quota, failover, plans
    guiDashboard, tray, settings UI
    on Oct 7, 2026
  3. Ingwannu commented on Oct 7, 2026

    @Ingwannu
    Owner

    Source triage at dev c30b522 confirms the classification gap you describe: src/oauth/kiro.ts:38-46 excludes invalid_request, kiroTokenRefreshError only preserves allowlisted codes, and src/oauth/index.ts:716 treats a Kiro 400/401 as terminal only when that preserved code exists. Thus this response does not set needsReauth through that branch. This is source confirmation, not an independent live-token reproduction.

    A blanket terminal classification needs care: AWS documents invalid_request as an input/request error, separately from invalid_grant and expired_token (CreateToken errors). Access-token expiry alone also does not establish that its refresh grant is dead. We should distinguish failed refresh/attention state from a definitively revoked grant, and retain the existing matching rotated-CLI-credential recovery before any terminal mark.

    Please confirm only whether the local Kiro CLI can still refresh this same session, and whether its client registration is still valid (yes/no/unknown is sufficient); do not attach client secrets, tokens, identities, or auth files. A fix needs mocked cases for this exact rejection, ordinary refreshable expiry, malformed-request rejection, rotated local credentials, transient/network errors, and a stale failure racing a successful credential replacement. Authentication changes retain independent maintainer review; no live credential replay is needed for the regression.

  4. lidge-jun commented on Oct 7, 2026

    @lidge-jun
    Owner

    Fixed on dev by #6716 (f9b96b7): a Kiro refresh rejected with invalid_request after CLI recovery fails now surfaces as needsReauth / not logged in with reauth_required health once the token expires, so the dashboard shows the re-auth action. It ships with the next release cut from dev.

  5. klvfn commented on Oct 7, 2026

    @klvfn
    Author

    Thanks for fixing this, @lidge-jun! I really appreciate it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansbugSomething isn't workingguiDashboard, tray, settings UI

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions