fix(agent-lifecycle): stop showing credentials_injection: failed for subscription agents (#612) - #663
Merged
Merged
Conversation
…subscription agents (#612) Subscription-mode agents authenticate via CLAUDE_CODE_OAUTH_TOKEN env var (SUB-002) and have no .credentials.enc file. The agent-start path ran inject_assigned_credentials unconditionally, the encryption service raised ValueError("No .credentials.enc file found in agent workspace"), and the caller's "not found" substring check missed it (the actual message contains "file found", not "not found"). The misleading status leaks to the start endpoint response and prompts operators to re-assign subscriptions / recreate containers — disrupting running state for no reason. Two-part fix: 1. CredentialsFileNotFoundError (ValueError subclass) in services/credential_encryption.py. import_to_agent now raises this specific type when .credentials.enc is absent, so callers can catch it without fragile substring matching. Subclassing ValueError preserves the admin import endpoint's existing 400 behavior — that path *should* surface a real error when the operator explicitly asked for an import. 2. Subscription-mode short-circuit in inject_assigned_credentials. db.get_agent_subscription_id() check fires before the import path runs; returns {"status": "skipped", "reason": "subscription_mode", "detail": "..."} with explicit mention of CLAUDE_CODE_OAUTH_TOKEN so operators see this is by design. The substring-match path is removed entirely — the explicit exception subclass replaces it. Tests: tests/unit/test_inject_assigned_credentials.py — 8 cases: - subscription mode short-circuits without consulting the service - file-missing returns skipped (not failed) - CredentialsFileNotFoundError remains a ValueError (admin path compat) - happy path still returns success with file list - empty decrypted dict returns skipped - other ValueError shapes retry then surface as failed - transient ValueError recovers on retry - missing CREDENTIAL_ENCRYPTION_KEY returns skipped Combined with the existing #421 skip-inject tests: 32 tests pass, no regression. Issue: #612 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
vybe
approved these changes
May 5, 2026
vybe
left a comment
Contributor
There was a problem hiding this comment.
Clean two-part fix — exception subclass removes the fragile substring match and the subscription short-circuit fires before the service is even consulted. 8 unit tests cover all the edge cases. LGTM.
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #612
Summary
Subscription-mode agents authenticate via
CLAUDE_CODE_OAUTH_TOKENenv var (SUB-002) and have no.credentials.encfile. The agent-start path raninject_assigned_credentialsunconditionally, the encryption service raisedValueError("No .credentials.enc file found in agent workspace"), and the caller's"not found" in str(e).lower()substring check missed it (the actual message contains"file found", not"not found").The misleading
{"credentials_injection": "failed"}leaks to the agent-start response and prompts operators to re-assign subscriptions or recreate containers — disrupting running state for no reason. We hit this exact false alarm earlier today restartingagent-trinity-systemafter a subscription assignment.Fix
Two-part:
CredentialsFileNotFoundError(ValueErrorsubclass) inservices/credential_encryption.py.import_to_agentraises this specific type when.credentials.encis absent, so callers can catch it without fragile substring matching. SubclassingValueErrorpreserves the admin import endpoint's existing 400 behavior — that path should surface a real error when the operator explicitly asks for import.Subscription-mode short-circuit in
inject_assigned_credentials.db.get_agent_subscription_id()check fires before the import path runs; returns{"status": "skipped", "reason": "subscription_mode", "detail": "agent authenticates via CLAUDE_CODE_OAUTH_TOKEN; file-based credential injection is not used"}with explicit mention of the env-var path so operators see this is by design.The substring-match path is removed entirely — the explicit exception subclass replaces it.
Why two changes
Either alone would close the cosmetic bug, but together they're load-bearing:
import_to_agent(a no-op for them) and waste retry budget on the doomed path.Together they short-circuit the common case AND make the rare case (subscription agent that also has a
.credentials.encfor non-Anthropic creds) report cleanly if it ever fails.Tests
tests/unit/test_inject_assigned_credentials.py— 8 cases:CredentialsFileNotFoundErrorremains aValueError(admin path compat)ValueErrorshapes retry then surface as failedValueErrorrecovers on retryCREDENTIAL_ENCRYPTION_KEYreturns skippedTest plan
.env){"credentials_injection": "skipped", "credentials_result": {"reason": "subscription_mode", ...}}🤖 Generated with Claude Code