Skip to content

fix(agent): hot-init providers on session restore credential miss (#74)#75

Open
Kantosaurus wants to merge 1 commit into1jehuang:masterfrom
Kantosaurus:fix/reload-anthropic-hot-init
Open

fix(agent): hot-init providers on session restore credential miss (#74)#75
Kantosaurus wants to merge 1 commit into1jehuang:masterfrom
Kantosaurus:fix/reload-anthropic-hot-init

Conversation

@Kantosaurus
Copy link
Copy Markdown

@Kantosaurus Kantosaurus commented Apr 29, 2026

Summary

Fixes #74.

When restoring a session, jcode prompts for an OAuth login on every reload even though ~/.jcode/auth.json already holds valid, unexpired Claude credentials. The user-visible message is:

Reload complete — continuing because a recovery directive was pending.

Claude OAuth Login (account: claude-1)

Opening browser for authentication...

The log evidence (see #74) shows the on-disk credentials work fine before and after the prompt — they only stop "being seen" during the session-restore window.

Root cause

Agent::new_with_session (src/agent.rs) and Agent::restore_session (src/agent/turn_execution.rs) call provider.set_model(&model) to re-apply the persisted model selection. When the running MultiProvider was constructed before an Anthropic sub-provider could be hot-initialised — e.g. across a restart-snapshot auto-restore where credentials were written between the original auth probe and the reload — set_model_on_provider returns:

Claude credentials not available. Run `jcode login --provider claude` first.

The error was logged via logging::error(...) and silently swallowed; the provider stayed in its credential-less state and any subsequent attempt to use it tripped the OAuth flow. Meanwhile, MultiProvider::on_auth_changed() is exactly the entry point that re-scans ~/.jcode/auth.json and hot-initialises the Anthropic provider — but it was only invoked from the login flow.

Fix

Introduce set_session_model_with_lazy_auth_init in src/agent.rs. It calls provider.set_model(model) and, on failure, invokes provider.on_auth_changed() once and retries. on_auth_changed is idempotent and cheap (a few file reads), and a no-op for already-configured providers. If the second attempt still fails, the original error is returned verbatim so genuinely missing credentials produce the same message as before.

Both session-restore call sites now go through this helper.

Tests

Added three unit tests in src/agent_tests.rs:

  • set_session_model_with_lazy_auth_init_recovers_after_on_auth_changed — stub provider whose first set_model returns the canonical "credentials not available" error, and whose on_auth_changed flips an internal flag. Verifies the helper returns Ok, set_model is called exactly twice, and on_auth_changed is called exactly once.
  • set_session_model_with_lazy_auth_init_returns_original_error_on_persistent_failure — when the provider never has credentials, the original error message is preserved.
  • set_session_model_with_lazy_auth_init_does_not_retry_when_first_call_succeedson_auth_changed is never invoked on the success path (no perf regression for the common case).

Note: cargo check passes locally; full cargo test --no-run was still compiling at submission time and will be validated on the reviewer's hardware / CI.

Diff

 src/agent.rs                |  36 +++++++-
 src/agent/turn_execution.rs |   2 +-
 src/agent_tests.rs          | 197 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 233 insertions(+), 2 deletions(-)

Risk

Low. The behaviour change is strictly additive on the failure path:

  • Success path: identical (one set_model call, no on_auth_changed).
  • Failure path: one extra on_auth_changed call (already used safely from the login flow) plus one set_model retry. Worst-case latency on a true credential miss is a few file-system reads.

No public APIs change. The new free function is pub(crate).


View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews
When restoring a session, `Agent::new_with_session` and
`Agent::restore_session` (in turn_execution) call
`provider.set_model(&model)` to re-apply the persisted model selection.
If the running `MultiProvider` was constructed before an Anthropic
sub-provider could be hot-initialised (e.g. across a `restart-snapshot`
auto-restore where credentials were written between the original probe
and reload), `set_model` returns:

    Claude credentials not available. Run `jcode login --provider claude` first.

The error was logged and silently swallowed. The user-visible symptom
was: jcode prompts for an OAuth login on every reload even though
`~/.jcode/auth.json` already holds valid, unexpired credentials. The
log evidence (1jehuang#74) shows the same on-disk credentials succeed
immediately after a manual `Hot-initialized Anthropic provider after
auth change` event.

Fix: introduce `set_session_model_with_lazy_auth_init` which retries
`set_model` exactly once after invoking `provider.on_auth_changed()`,
the same hook the login flow uses. `on_auth_changed` is idempotent and
cheap (a few file reads), and is a no-op for providers that are already
configured. If the second attempt still fails, the original error is
preserved verbatim so the existing failure mode (and message) is
unchanged for genuine credential-missing cases.

Both session-restore call sites (`Agent::new_with_session` in
`src/agent.rs` and `Agent::restore_session` in
`src/agent/turn_execution.rs`) now go through this helper.

Tests:
- recovers_after_on_auth_changed: a stub provider whose first
  `set_model` fails with the canonical error, and whose
  `on_auth_changed` flips an internal credentials-loaded flag, succeeds
  on the second attempt with exactly one `set_model` retry and one
  `on_auth_changed` call.
- returns_original_error_on_persistent_failure: when the underlying
  provider keeps failing, the original error message ("credentials not
  available") is preserved.
- does_not_retry_when_first_call_succeeds: `on_auth_changed` is never
  called on the success path.

Fixes 1jehuang#74
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant