Skip to content

Regression introduced by merged PR #23068 -- bug fix #30734

Description

@FlorianOtel

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

  1. 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).
  2. An OpenCode server running on a known port (default 4096).
  3. 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.
  4. 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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions