Description
When a PTY process spawned with notifyOnExit: true exits, the plugin sends the <pty_exited> notification via client.session.promptAsync with only agent: session.parentAgent and no model in the body (see dist/src/plugin/pty/notification-manager.js, sendExitNotification).
In OpenCode's model resolution chain (packages/opencode/src/session/prompt.ts, createUserMessage):
input.model ?? ag.model ?? currentModel(sessionID)
a model-less prompt falls back to the agent's configured model (ag.model). The turn is then answered by the agent's default model instead of the model the user currently has selected in the session. Worse, createUserMessage subsequently calls setAgentModel, which persists that resolved model back into the session row — so the user's manual model selection is permanently clobbered and all subsequent model-less injections (e.g. oh-my-openagent's TODO-continuation prompts, which inherit the model from the most recent message) also resolve to the agent's default model.
This is the plugin-side half of the problem tracked upstream at anomalyco/opencode#42893 ("Model swap gets clobbered by model-less prompts"). The TUI never triggers it because it always sends an explicit model on every prompt.
Reproduction Steps
- Configure a primary agent with a pinned model in
opencode.json (e.g. agent "build" → model A)
- Start a session, then switch the model in the picker to model B (e.g. a stronger model for a heavy task)
- Ask the agent to run a long background render, e.g.
pty_spawn with notifyOnExit: true
- While it runs, keep chatting (turns run on model B)
- The PTY exits →
<pty_exited> notification is injected without a model
- The reply to the notification runs on model A, and the session row is rewritten to model A
- All following turns (including other plugins' model-less injections) run on model A until the user manually sends another message
Verified in opencode.db (session with agent pinned to glm-5.3, user switched to opencode/x-preview-f-free):
user opencode/x-preview-f-free ← user's manual message (model B)
user opencode/x-preview-f-free ← user's manual message (model B)
user volcengine-agent-plan/glm-5.3 ← <pty_exited> injection (reverted to model A)
user volcengine-agent-plan/glm-5.3 ← oh-my-openagent TODO CONTINUATION (inherited the clobbered model A)
user opencode/x-preview-f-free ← next manual message ("继续") re-pins model B
Expected Behavior
The <pty_exited> notification should preserve the session's currently selected model, so the wake-up turn is answered by the model the user picked, and the session's model selection survives background-process completion.
Actual Behavior
The notification is dispatched without a model, OpenCode resolves the turn to the agent's configured model, and the session's persisted model is overwritten — silently switching the user to a different (potentially metered) model for the remainder of the session.
Environment
- OS: NixOS (Linux x86_64)
- OpenCode Version: 1.18.21
- Plugin Version: 0.3.6 (
opencode-pty@latest)
- Bun Version: (plugin runs under OpenCode's plugin host)
OpenCode Configuration
opencode.json (sanitized)
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
"opencode-pty@latest",
"oh-my-openagent@latest"
]
}
Suggested Fix
sendExitNotification already has the parent session id; fetch the session's current model and pass it through:
async sendExitNotification(session, exitCode) {
if (!this.client) return;
try {
const message = this.buildExitNotification(session, exitCode);
// Preserve the user's currently selected model across the injected wake-up turn
let model;
try {
const res = await this.client.session.get({ path: { id: session.parentSessionId } });
const m = res?.data?.model ?? res?.model; // { id, providerID, variant }
if (m?.providerID && m?.id) {
model = { providerID: m.providerID, modelID: m.id, ...(m.variant ? { variant: m.variant } : {}) };
}
} catch { /* fall back to model-less prompt */ }
await this.client.session.promptAsync({
path: { id: session.parentSessionId } },
body: {
parts: [{ type: 'text', text: message }],
...(session.parentAgent ? { agent: session.parentAgent } : {}),
...(model ? { model } : {}),
},
});
} catch {
// Ignore notification errors
}
}
This mirrors what the TUI does on every prompt (it always sends model: selectedModel). Note there is an inherent race if the user switches models between session.get and the dispatch, but that window is negligible compared to the current behavior of always reverting.
A deeper fix on the OpenCode side (guarding setAgentModel against model-less overwrites) is being tracked in anomalyco/opencode#42893 — this issue covers the plugin side.
Debug Logs
Click to expand debug logs
Not applicable — the misbehavior is fully explained by the missing model field in the promptAsync body (notification-manager.js L13–19), confirmed by inspecting the stored user-message model fields in ~/.local/share/opencode/opencode.db.
Additional Context
Related historical issue: #23 ("PTY exit notifications to use spawning conversation agent") fixed the agent half of this problem; the model half remains. Downstream amplification: oh-my-openagent's continuation hooks resolve the model from the most recent session message, so one model-less <pty_exited> injection poisons every subsequent model-less injection until the user manually sends a message.
Description
When a PTY process spawned with
notifyOnExit: trueexits, the plugin sends the<pty_exited>notification viaclient.session.promptAsyncwith onlyagent: session.parentAgentand nomodelin the body (seedist/src/plugin/pty/notification-manager.js,sendExitNotification).In OpenCode's model resolution chain (
packages/opencode/src/session/prompt.ts,createUserMessage):a model-less prompt falls back to the agent's configured model (
ag.model). The turn is then answered by the agent's default model instead of the model the user currently has selected in the session. Worse,createUserMessagesubsequently callssetAgentModel, which persists that resolved model back into the session row — so the user's manual model selection is permanently clobbered and all subsequent model-less injections (e.g. oh-my-openagent's TODO-continuation prompts, which inherit the model from the most recent message) also resolve to the agent's default model.This is the plugin-side half of the problem tracked upstream at anomalyco/opencode#42893 ("Model swap gets clobbered by model-less prompts"). The TUI never triggers it because it always sends an explicit
modelon every prompt.Reproduction Steps
opencode.json(e.g. agent "build" → model A)pty_spawnwithnotifyOnExit: true<pty_exited>notification is injected without a modelVerified in
opencode.db(session with agent pinned toglm-5.3, user switched toopencode/x-preview-f-free):Expected Behavior
The
<pty_exited>notification should preserve the session's currently selected model, so the wake-up turn is answered by the model the user picked, and the session's model selection survives background-process completion.Actual Behavior
The notification is dispatched without a
model, OpenCode resolves the turn to the agent's configured model, and the session's persisted model is overwritten — silently switching the user to a different (potentially metered) model for the remainder of the session.Environment
opencode-pty@latest)OpenCode Configuration
opencode.json (sanitized)
{ "$schema": "https://opencode.ai/config.json", "plugin": [ "opencode-pty@latest", "oh-my-openagent@latest" ] }Suggested Fix
sendExitNotificationalready has the parent session id; fetch the session's current model and pass it through:This mirrors what the TUI does on every prompt (it always sends
model: selectedModel). Note there is an inherent race if the user switches models betweensession.getand the dispatch, but that window is negligible compared to the current behavior of always reverting.A deeper fix on the OpenCode side (guarding
setAgentModelagainst model-less overwrites) is being tracked in anomalyco/opencode#42893 — this issue covers the plugin side.Debug Logs
Click to expand debug logs
Not applicable — the misbehavior is fully explained by the missing
modelfield in thepromptAsyncbody (notification-manager.jsL13–19), confirmed by inspecting the stored user-messagemodelfields in~/.local/share/opencode/opencode.db.Additional Context
Related historical issue: #23 ("PTY exit notifications to use spawning conversation agent") fixed the
agenthalf of this problem; themodelhalf remains. Downstream amplification: oh-my-openagent's continuation hooks resolve the model from the most recent session message, so one model-less<pty_exited>injection poisons every subsequent model-less injection until the user manually sends a message.