Conversation
wrapSessionProvider forwards a fixed list of the optional AgentSession methods and steerActiveTurn was not on it, so any wrapped session lost the method entirely. steerOrReplaceActiveTurn then took its unavailable branch and replaced the running turn instead of steering it. That hits every custom provider, and a built-in provider as soon as profileModels or additionalModels is configured. The claude, codex and opencode providers all implement steerActiveTurn, so with Default send set to Steer a mid-turn message canceled the turn and the tool call in flight came back denied. The wrap test already walks the optional methods, so steerActiveTurn joins that walk and the exhaustiveness list next to it.
76ed237 to
75c309c
Compare
|
Thank you for isolating the wrapper bug. PR #4154 includes your exact commit |
|
Thanks for the heads-up, and for keeping the authorship on it. Useful to know the generic-ACP path needs the same forwarder — that's a second Happy to close this one if the maintainers would rather take #4154; the change |
|
Confirmed this works. Running v0.8.0 with a custom |
Linked issue
Closes #4112
Type of change
Reasoning
With Default send: Steer, sending a message to a working agent was canceling
its turn and starting a new one. The tool call in flight came back as denied and
the agent restarted from the new message — the exact thing steering exists to
avoid. It looked random because it depends on the provider entry the agent was
created under, not on the agent or the model.
wrapSessionProviderforwards a fixed list of the optionalAgentSessionmethods and
steerActiveTurnwas not on it, so a wrapped session did not havethe method at all.
steerOrReplaceActiveTurnthen took its{ status: "unavailable" }branch and replaced the turn, which is the correctbehavior for a provider that cannot steer and the wrong one here — the provider
underneath can.
createResolvedProviderClientwraps whenever the provider id differs from thebase client's, or
profileModels/additionalModelsis non-empty. So this hitevery custom provider, and the built-in
claudeprovider as soon as it hadmodel overrides in the config. The claude, codex and opencode providers all
implement
steerActiveTurn, so all three were affected; I reproduced andverified the fix on Claude Code.
Goals
same whether or not the provider has model overrides configured.
steerActiveTurn, so removing the forward again fails atest that actually runs.
Non-goals
tsconfig.server.jsonexcludessrc/**/*.test.ts, so no command evaluates it.Fixing that means bringing test files into
npm run typecheck, which surfacesunrelated errors in the same file's
FakeSession. Out of scope here; writtenup in bug: steering silently falls back to interrupt + replace for wrapped providers #4112 with the
tsgooutput.still replaces the turn.
QA
Before, on
main(63923fd), withsteerActiveTurnadded to the wrap test andthe wrapper untouched:
After, same command:
Related suites, unchanged:
I then ran the jobs this diff routes to in CI, with their prerequisite steps, on
Linux:
format:npm run format:check— clean, 4196 files.lint:lockfile-lint,npm audit signatures(347 packages verified), andnpm run lint— clean.typecheck:npm run build:server,npm run typecheck, and the threenpm pack --dry-runchecks — all pass.server-tests: aftergit fetch origin mainandnpm run build:server,npm run test:unitis 5234 passed / 51 skipped / 2 failed, andnpm run test:integrationis 12 passed plus 1 passed / 10 skipped.The two unit failures are not from this change:
src/utils/paseo-config-file.test.ts> "rejects stale writes when the currentrevision changed before rename" fails identically on unmodified
63923fd,including when run alone. Both writes land in the same
mtimeMs(
1788173252565.0713), so it looks like filesystem timestamp resolution here(ext4).
src/server/hub/hub-cli-contract.test.tstimes out at 30s, but only while all359 test files run in parallel. Run the way CI runs it —
npm run test:hub-cli-contract— it passes.Not run:
server-tests (windows-latest), no Windows machine here. The otherrequired checks (app, playwright, desktop, sdk, relay, cli) are filtered out for
this path, so I did not run them either. My runs were on Node 24 while CI pins
Node 22, and the globally installed agent CLIs here are newer than the versions
CI installs.
In the app: I applied the same one-line change to the installed 0.5.1 build,
restarted the daemon, and re-ran the case that had been failing all day — two
long-running agents under a
claudeprovider entry withadditionalModelsconfigured, a mid-turn message to each with Default send: Steer. Both were
steered; neither was interrupted. Before the change, both were replaced every
time.
Tested on Linux (Ubuntu 24.04.4), self-hosted daemon, Claude Code v2.1.241. The
codex and opencode providers take the same wrapper path but I did not test them
by hand. Not tested on macOS desktop, iOS or Android.
Checklist
npm run typecheckpassesnpm run lintpassesnpm run formatpasses