[BUG] Spawned subagents inherit the parent's creation-time model options — stale provider keeps 401ing until server restart #2904
Replies: 5 comments
|
Verified against the current checkout (master @ 1. Confirmed at source.
2. The mechanism boundary — why the parent switches but the child never does. 3. Fix design — three tiers.
4. Family + regression test. One question this raises that the report doesn't cover: continuable children on cold resume — a child resumed from disk re-creates from its persisted header (the stale one). Does the resume path re-resolve the route the same way |
|
Implemented and regression-tested a package-local fix for the stale child route described here. Patch commit: jzhao0@bead7b4 The change makes Regression coverage: one-shot and continuable RED→GREEN, relevant inheritance suites 14/14, and the assembled keyless snapshot fails against the old source and passes with the patch. I also tried to open an upstream PR, but the repository currently reports |
Implementation ready for reviewA working implementation of the fix described in this discussion is now available on a fork. Branch:
What changed (5 files, 215 insertions, 10 deletions)
Verification
Compatibility
Caveat (from the diagnosis)The per-session Happy to revise the implementation or split it into multiple commits if maintainers prefer a different shape. |
|
Cross-validating both implementations against the What's verified and correct
Gap — the durable descriptor still snapshots
const agentProvider = request.agentOptions?.provider ?? parent.options.provider
const agentModel = request.agentOptions?.model ?? parent.options.modelwhile
There is already a second implementation that closes this jzhao0's Suggested canonical patch when PRs re-enable Your waterfall (tier-1 + tier-2) + jzhao0's descriptor sync: resolve once, share the snapshot across One open design decision (from my original reply here) jzhao0's choice — "cold resume does not re-read the parent's current route" — is defensible (an established durable child shouldn't be retroactively re-routed), but it is precisely the question this thread asked and never got an answer to: a continuable child resumed after the parent switched models will run the old route for its entire remaining life. For the billing-misroute family, "stability" is exactly what the reports keep calling a silent bug. Maintainers should pick: stable-from-descriptor (jzhao0) or re-resolve-at-resume — and either way the descriptor must agree with the actual route, which requires the Also note the 中文摘要:对照 rc.2 验证了你的补丁(瀑布顺序 = 本线程蓝图的 tier-1+tier-2, |
|
补一条与楼上 DSH 原生补丁并行、可以直接使用的替代路线。它不修复官方 dsh plugin --profile <你的-profile> add pi2dsh
dsh plugin --profile <你的-profile> add @tintinweb/pi-subagentspi2dsh 的子代理路由解析现在优先读取父会话持久日志中最后一次真实请求的 provider/model,最后才回退创建时 options。因此旧 provider 已失效但父会话已经切到新 provider 时,新开的 Pi 子代理不会继续拿旧凭证 401。 真机验收不是看选择器文字:stock dsh-TUI 中父会话先请求官方线, 复现与证据: |
Uh oh!
There was an error while loading. Please reload this page.
Summary
Spawned subagents (
subagent/subagent_fork, in-processspawn/forkproviders) inherit the parent agent's creation-time model options instead of the session's live model selection. After the default model/provider changes mid-session (via$DSH_HOME/settings.yaml→agent-default-model, or a per-session/modelswitch), the parent's own requests route through the new provider, but every subsequently spawned child still routes through the old provider — with the old provider's credentials resolved per request. When those credentials are expired/revoked, 100% of subagent dispatches fail withAUTH 401while the parent session keeps working, until the server is restarted.Reproduction
webprofile (bundlesdsh-base+dsh-web-app). The base bundle shipsagent-default-modelpinned todeepseek-official:@deepseek-ai/dsh-base/cordis.patch.ymlrows 62-67:provider: deepseek-official, model: deepseek-v4-flashagentOptionstaken fromdefaultModelSelection()(see below)./modelUI.Expected: the child uses the session's current model selection (same provider the parent's own requests now use).
Actual: the child's first
request/headershows the parent's creation-time provider/model, and the first LLM call fails (observed:Authentication Fails… code AUTH, status 401) because the stale provider resolves a dead credential.Evidence from the session log (decompressed
session.jsonl.zstd):request/header(seq ~11):provider: deepseek-official(creation-time default).request/header(seq ~26):provider: opencode-go(after the default change).request/header:provider: deepseek-official(stale) →assistant/chunkfinisherror{code: AUTH, status: 401}.Root cause (from source)
packages/host/apiproxy/src/api-proxy.ts→agentOptions() = defaults.defaultModelSelection()packages/host/apiproxy/src/index.ts→defaultModelSelection: () => ctx.agentDefaultModel.currentSelection()agentDefaultModel.currentSelection()is settings-driven and evaluated at create/resume time. The snapshot lands in the agent's immutableoptions.api-proxyinstallsinstallModelSelection(...), which re-reads a per-session selection waterfall — picked selection → session's latest loggedrequest/header→ live default — on every request. That is why the parent switches cleanly whileagent.optionsstays frozen.packages/subagent/subagent/src/child-agent.ts→resolveChildAgentOptions(parent, requested, depth), which copiesparent.options.provider/model/maxTokensand only then spreads any per-childagentOptionsoverride. Nothing feeds the session's live selection or the current default into that copy, so children pin the parent's creation-time route indefinitely.Suggested fix
Children should resolve their route from the same live waterfall the parent uses per request (session selection → latest logged
request/header→agentDefaultModel.currentSelection()), falling back toparent.optionsonly when none of those exist — or, minimally, the spawned-child options should re-readagentDefaultModel.currentSelection()when the parent's options differ from it. An escape hatch already exists per-tool (tool-subagentconfigagentOptions, spread last so it wins), but it is per-deployment config, not a fix for the default path.Affected versions / environment
dsh-base+dsh-web-appbundles), latest installed as of 2026-08-17spawnandforksubagent providers (tool-subagent,tool-subagent-fork),backgroundMode: continuable$DSH_HOME/settings.yamlagent-default-modelsection plus$DSH_HOME/.credentials.yamlprovider keysWorkarounds (until fixed)
agentOptions: {provider, model}on thetool-subagent/tool-subagent-forkpreset rows (spread last inresolveChildAgentOptions, so it wins over the inherited parent route). Note the preset composition is stamped: edits apply to sessions created afterwards.Reported from local diagnosis only; no keys or credentials are included in this report. The failing provider line was confirmed from child session logs (
request/header+assistant/chunkfailure records), not from any secret material.All reactions