Skip to content

fix(agents): existing agents pick up a later-connected key; stop advising an impossible login - #323

Open
stubbi wants to merge 2 commits into
mainfrom
fix/builtin-credential-backfill
Open

fix(agents): existing agents pick up a later-connected key; stop advising an impossible login#323
stubbi wants to merge 2 commits into
mainfrom
fix/builtin-credential-backfill

Conversation

@stubbi

@stubbi stubbi commented Aug 8, 2026

Copy link
Copy Markdown

Both fixes come out of one hosted company's report: since subscribing, not a
single agent run had ever succeeded, including one from the built-in
Summarizer with untouched factory configuration.

The sandbox fault behind their 8 failed runs was #322. Chasing the rest of
their account surfaced two further faults, one of which affects every signup.

1. An agent that already exists never picks up a later-connected key

inheritCompanyCredentialEnv ran only on the create path. The ordinary order
of events is the opposite: the company and its built-in agents exist from
signup, the user connects a key afterwards, and nothing re-runs inheritance.

Ground truth for the reporting company:

agent env binding status
Summarizer (built-in) none paused: "Connect a model key to run this agent."
Reflection Coach (built-in) none paused (by design)
the one they made by hand env.CLAUDE_CODE_OAUTH_TOKEN paused (manual)

Their company secret was active and company-scoped the whole time. A stock
built-in agent could not run and nothing explained why.

Now re-run on the built-in ensure path and after a credential lands via PATCH.
Same donor rule as create time, agents with their own credential untouched,
second pass is a no-op. The decision is a pure function over the agent rows,
so it is tested without a database.

2. "not logged in" advised an interactive /login

The adapter surfaced the CLI's line verbatim. A Paperclip run cannot perform
an interactive login: it is headless, and on a managed sandbox the pod dies
with the lease. The rejected-credential sibling case already got a written
remedy; this gives the same to "not logged in", in the shape codex-local
already uses.

The live trigger: the reporting user's Claude OAuth token is revoked at
Anthropic. Decrypted in-pod it is well-formed (len 108, sk-ant-oat01-
prefix, no whitespace, stored fingerprint matches), so nothing looks wrong to
them; api.anthropic.com answers 401 OAuth access token has been revoked.
Minting a fresh token is the only thing that helps, so the message says so.

Classification is unchanged and the raw CLI text stays in resultJson.

🤖 Generated with Claude Code

stubbi and others added 2 commits August 8, 2026 08:56
…ed key

Credential inheritance ran only while an agent was being created. That is the
wrong moment for the ordinary case: a company and its built-in agents exist
from signup, and the user connects a provider key afterwards. Nothing re-ran
inheritance, so those agents stayed credential-less permanently.

A hosted company hit the full consequence. Their Claude token was active and
company-scoped, but bound to the single agent they had made by hand. Both
built-in agents had no env binding at all, and the built-in Summarizer sat
paused on "Connect a model key to run this agent" while its configuration was
untouched factory default. From the user's side a stock agent simply could not
run, and nothing on screen explained why.

Two places now re-run the same donor rule over agents that already exist:

  - the built-in ensure path, which every built-in agent goes through, so one
    provisioned before any key existed picks the company credential up;
  - after a credential lands on any agent via PATCH, so the rest of the
    company (built-ins included) is wired at the moment of connecting rather
    than whenever that agent is next ensured.

The rule is unchanged from create time: same CEO-preferred donor, same merge,
and an agent that already carries its own credential is never touched. That
makes a second pass a no-op, so repeated connects cannot rewrite anything, and
a deliberate per-agent key always wins. The decision is a pure function taking
the agent rows, so the behaviour is covered without a database.

Resuming stays a user action. A paused agent's reason already says to resume,
and auto-resuming an agent that spends the user's provider quota is not ours
to decide.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…not run

When the Claude CLI reports "not logged in" it advises an interactive `/login`,
and the adapter surfaced that line verbatim as the run error. A Paperclip run
can never perform it: the run is headless, and on a managed sandbox the pod is
destroyed with the lease. A paying hosted user was left chasing a command that
does not exist for them, with no hint that the real fix is to reconnect the
credential.

The sibling case was already handled: a credential that is present but rejected
resolves to CLAUDE_INVALID_CREDENTIAL_MESSAGE rather than the provider's raw
401. This gives "not logged in" the same treatment, leading with the remedy
that works here and keeping host login as the self-hosted footnote, which is
the shape codex-local already uses for its managed-home credential error.

The trigger in the wild was a Claude OAuth token revoked at Anthropic. It is
worth naming that case in the message: the token is well-formed and stored
intact, so nothing about it looks wrong to the user, and "mint a fresh one"
is the only thing that helps.

Classification is unchanged (still claude_auth_required, still covered by the
permanent-auth pause) and the raw CLI text is still kept in resultJson.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant