Skip to content

Every Terminal credential failure shows one message, so users cannot tell a denied Keychain from corrupt JSON #225

Description

@Nanako0129

Two users a week apart have now reported the same red card text, from different root causes, and the text cannot tell them apart. Verified on main (9a15a14d).

Claude credentials could not be loaded. is what every ClaudeLoginResolution::Terminal produces. Only Absent and ExplicitLogout reach the setup-token fallback (agent_usage.rs:1885-1892), so a Terminal verdict also silently skips the tokenbar-claude-oauth-token route the in-app prompt and README tell people to use — which is what makes the ambiguity actionable rather than cosmetic.

The diagnostic strings already exist and are thrown away

Each lower layer produces a distinguishable message. All of them are discarded at the resolution boundary by Err(_).

Produced at Existing string Discarded at
agent_usage.rs:2755 read Claude Keychain credentials: {spawn error} :2553
agent_usage.rs:2760 Claude Keychain credentials could not be read.any security exit other than 44, including a denied ACL :2553
agent_usage.rs:2764 ...are not UTF-8 JSON. :2553
agent_usage.rs:2767 ...are empty. :2553
agent_usage.rs:2535 Claude credentials file could not be read. :2558
agent_usage.rs:2683 / :2685 decode Claude OAuth credentials: {serde error} :2591
agent_usage.rs:2693 ...have no access token. :2591
agent_usage.rs:2726 Claude credential location cannot be scoped safely. :2591

I confirmed locally that security find-generic-password exits 44 when an item is absent and 0 on success, so keychain_item_not_found (:2735) routes absence to Ok(None) correctly. Every other failure — a denied ACL, a locked keychain, errSecInteractionNotAllowed — lands in the same bucket as corrupt JSON.

The card is already able to show it

No new UI is needed. detailText returns snapshot.error verbatim when present (AgentLimitsCard.swift:438-443), and the card renders it in red with lineLimit(2) plus the full string in .help() (:338-343). A longer message degrades gracefully into the tooltip.

Proposal

  • Give Terminal a payload (agent_usage.rs:1022) and stop discarding the string at the four Err(_) arms.
  • Include the security exit code in the Keychain read-failure message (:2756-2761). One number separates a denied ACL from errSecInteractionNotAllowed from a spawn failure, without a round trip through an issue thread.
  • Say in the text that the setup-token fallback was skipped deliberately, and what to do next. CLAUDE_UNCONFIGURED_ERROR (:2517) is the existing model for a message that names an action.

Two adjacent defects to fix in the same pass:

  • :1902-1907 — when the login is Absent and the setup-token Keychain read fails, it emits the same CLAUDE_CREDENTIALS_LOAD_ERROR. Different failure, identical text.
  • :2528Err(_) => Terminal for the environment loader is unreachable. load_claude_credentials_from_environment (:2635-2676) contains no Err( at all.

Deliberately not proposed

No Terminal sub-case should be reclassified. Each was analysed separately; every one is damage or unprovable rather than absence, and the cross-account argument from #94 holds for all of them. {"claudeAiOauth":{}} is the most tempting and fails the same way the fall-through in #221 did: nobody can prove the shape is not a logout remnant, and there is no reported case of it. After #221 the classification split is correct — what remains is purely observability.

No Keychain-to-file fall-through on a read failure. Same defect #221's security review rejected as P2: the file may predate a logout the Keychain already recorded.

Do not migrate the credential read to the Security framework. Keychain ACL is evaluated against the process talking to securityd, which for a shell-out is /usr/bin/security — stably signed by Apple — not TokenBar. A user's one-time "Always Allow" therefore survives TokenBar updates. Because TokenBar ships ad-hoc signed (docs/knowledge/release.md, "Code signing and local secret storage"), its own designated requirement changes on every update, so calling the framework directly would make the requesting identity TokenBar itself and re-break access on each release. The deferred cleanup about security shell-outs is motivated by argv exposure, which applies to the write path; the read path passes -w for output, not a secret in argv.

Context

Raised by @coshsh1991 in #219 as the third suggestion — "at minimum, surface a distinct error so users know the setup-token path is being skipped" — which v1.13.3 did not address; only option (a) shipped. @qodeboy then hit the red text on v1.13.3 with a different cause, reporting no Keychain permission prompt, which is consistent with a read failure rather than the shape #221 fixed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions