Description
What ddc30cd15 was supposed to fix
- Commit:
ddc30cd15 ? feat(core): add session metadata support (#23068)
- Date: 2026-05-30 21:58 UTC
- PR purpose: Add explicit
agent and model fields (alongside a new metadata field) to the Session.create() / createNext() input shape. Prior to this refactor, those values lived implicitly inside the session creation flow; the PR's goal was to make them first-class, explicitly-passed inputs so that callers control them directly rather than relying on context-derived defaults.
- Affected surfaces:
packages/opencode/src/session/session.ts ? create() (~lines 740-785) and createNext() (~lines 580ff) now accept agent?: string and model?: Schema.Schema.Type<typeof Model> in their input shape.
packages/core/src/session/projector.ts ? sessionRow() (~lines 38-70) extracts these fields directly from the Info object when writing the DB row.
Regression it introduced
- Caller never updated.
packages/opencode/src/tool/task.ts lines ~147-162 ? the only call site that creates a child session for a dispatched subagent ? still calls sessions.create() with only parentID, title, and permission. It never passes agent or model, even though the refactor made these explicit input fields.
- The model value is already computed at the same call site but never used at create. Lines ~170-198 of the same file derive
const model = next.model ?? { modelID: msg.info.modelID, providerID: msg.info.providerID } and use it for the prompt invocation and metadata publish ? but never thread it back to the sessions.create() call eighteen lines above.
- Historic projector-based back-fill is no longer dependable. Before
ddc30cd15, the DB columns were populated by projector handlers reacting to SessionEvent.AgentSwitched and SessionEvent.ModelSwitched events that session/prompt.ts emits on first prompt (the current?.agent !== info.agent comparison fires the event because current is undefined for a fresh session). After the refactor, this path no longer reliably populates the columns on Task-tool-spawned children ? likely because the emitted event payloads or comparison surfaces shifted, but the practical observable effect is unambiguous: child session rows have agent IS NULL, model IS NULL.
Net effect
- Every child session created via the Task tool from
ddc30cd15 onwards has agent IS NULL and model IS NULL in the SQLite session table.
- Downstream consumers (any tool that reads
~/.local/share/opencode/opencode.db for subagent attribution ? telemetry, status-line, ad-hoc SQL, future tooling) cannot determine which subagent ran or which model handled a child session.
- The breakage is in upstream OpenCode
dev HEAD AND in release v1.15.13 and later (since ddc30cd15 was tagged into v1.15.13 on 2026-05-30 23:40 UTC, ~1h42m after the bug landed).
v1.15.12 and earlier are unaffected.
Plugins
None
OpenCode version
The reproduction only requires an OpenCode daemon built from dev HEAD (or any release v1.15.13+).
Steps to reproduce
Prerequisites
- A built
opencode binary from dev HEAD (or any release v1.15.13+). Verify version contains 0.0.0-{branch}-{timestamp} (local build) or 1.15.13+ (release).
- An OpenCode server running on a known port (default
4096).
- At least one agent definition installed at
~/.config/opencode/agents/<some-agent>.md with at minimum:
---
name: minimal-test-agent
description: Synthetic agent for reproducing the agent/model regression.
model: anthropic/claude-haiku-4-5
tools:
Read: true
---
You are a synthetic test agent. Respond with the literal string "ok" and nothing else.
sqlite3 CLI available.
Steps
# 1. Pick a synthetic working directory (not a real project ? avoids cross-test pollution)
TMPDIR=$(mktemp -d /tmp/oc-regression-XXXXXX)
cd "$TMPDIR"
# 2. Create a top-level (parent) session via SDK or REST against the running daemon.
# Capture the returned session ID as PARENT_SID.
PARENT_SID=$(curl -sS -X POST \
-H "x-opencode-directory: $TMPDIR" \
-H "Content-Type: application/json" \
-d '{"title":"regression-repro-parent"}' \
"http://localhost:4096/session" | jq -r '.id')
echo "PARENT_SID=$PARENT_SID"
# 3. Send a user message that triggers a Task tool call dispatching the test agent.
# The exact request shape depends on the SDK version; the goal is one assistant
# turn that calls Task with subagent_type: "minimal-test-agent".
# Pseudocode:
#
# POST /session/{PARENT_SID}/message
# {
# "messageID": "msg_<descending_ulid>",
# "model": { "modelID": "claude-haiku-4-5", "providerID": "anthropic" },
# "agent": "general",
# "parts": [{ "type": "text", "text": "Dispatch the minimal-test-agent via the Task tool with prompt: ok" }]
# }
#
# The assistant turn that follows will invoke Task, which creates a child session.
# 4. Wait for session-idle (the child session is now created and the agent has responded).
# 5. Query the SQLite session table for the child session row.
sqlite3 ~/.local/share/opencode/opencode.db <<SQL
SELECT id, agent, model, parent_id
FROM session
WHERE parent_id = '$PARENT_SID';
SQL
Observed (broken) output
ses_<child_id>|||ses_<parent_id>
(agent and model columns empty / NULL.)
Expected (post-fix) output
ses_<child_id>|minimal-test-agent|{"id":"claude-haiku-4-5","providerID":"anthropic","variant":"default"}|ses_<parent_id>
(agent populated with the dispatched subagent's name; model populated with the resolved model JSON.)
Pure-unit reproduction (no daemon, no DB)
A faster reproduction that doesn't need a running daemon is to call Session.create() directly with no agent/model (the same way task.ts does today) and assert via the projector that the row written to the DB has NULL agent and model. See §4 step 2 for the test shape ? this is the failing test that will be written first.
Screenshot and/or share link
None needed
Operating System
Linux Debian testing
Terminal
xterm
Description
What
ddc30cd15was supposed to fixddc30cd15?feat(core): add session metadata support (#23068)agentandmodelfields (alongside a newmetadatafield) to theSession.create()/createNext()input shape. Prior to this refactor, those values lived implicitly inside the session creation flow; the PR's goal was to make them first-class, explicitly-passed inputs so that callers control them directly rather than relying on context-derived defaults.packages/opencode/src/session/session.ts?create()(~lines 740-785) andcreateNext()(~lines 580ff) now acceptagent?: stringandmodel?: Schema.Schema.Type<typeof Model>in their input shape.packages/core/src/session/projector.ts?sessionRow()(~lines 38-70) extracts these fields directly from theInfoobject when writing the DB row.Regression it introduced
packages/opencode/src/tool/task.tslines ~147-162 ? the only call site that creates a child session for a dispatched subagent ? still callssessions.create()with onlyparentID,title, andpermission. It never passesagentormodel, even though the refactor made these explicit input fields.const model = next.model ?? { modelID: msg.info.modelID, providerID: msg.info.providerID }and use it for the prompt invocation and metadata publish ? but never thread it back to thesessions.create()call eighteen lines above.ddc30cd15, the DB columns were populated by projector handlers reacting toSessionEvent.AgentSwitchedandSessionEvent.ModelSwitchedevents thatsession/prompt.tsemits on first prompt (thecurrent?.agent !== info.agentcomparison fires the event becausecurrentisundefinedfor a fresh session). After the refactor, this path no longer reliably populates the columns on Task-tool-spawned children ? likely because the emitted event payloads or comparison surfaces shifted, but the practical observable effect is unambiguous: child session rows haveagent IS NULL, model IS NULL.Net effect
ddc30cd15onwards hasagent IS NULLandmodel IS NULLin the SQLitesessiontable.~/.local/share/opencode/opencode.dbfor subagent attribution ? telemetry, status-line, ad-hoc SQL, future tooling) cannot determine which subagent ran or which model handled a child session.devHEAD AND in releasev1.15.13and later (sinceddc30cd15was tagged intov1.15.13on 2026-05-30 23:40 UTC, ~1h42m after the bug landed).v1.15.12and earlier are unaffected.Plugins
None
OpenCode version
The reproduction only requires an OpenCode daemon built from
devHEAD (or any releasev1.15.13+).Steps to reproduce
Prerequisites
opencodebinary fromdevHEAD (or any releasev1.15.13+). Verify version contains0.0.0-{branch}-{timestamp}(local build) or1.15.13+(release).4096).~/.config/opencode/agents/<some-agent>.mdwith at minimum:sqlite3CLI available.Steps
Observed (broken) output
(
agentandmodelcolumns empty / NULL.)Expected (post-fix) output
(
agentpopulated with the dispatched subagent's name;modelpopulated with the resolved model JSON.)Pure-unit reproduction (no daemon, no DB)
A faster reproduction that doesn't need a running daemon is to call
Session.create()directly with noagent/model(the same waytask.tsdoes today) and assert via the projector that the row written to the DB has NULLagentandmodel. See §4 step 2 for the test shape ? this is the failing test that will be written first.Screenshot and/or share link
None needed
Operating System
Linux Debian testing
Terminal
xterm