Skip to content

[Bug]: non-session task_id reaches native task after before-hook #1038

Description

@ybw0014

What happened, and what did you expect?

oh-my-opencode-slim exposes short Background Job Board aliases for specialist-session reuse. The orchestrator prompt (src/agents/orchestrator.ts) instructs:

"When reusing a specialist session, you MUST pass the existing session or alias in the task tool's task_id argument. If the Background Job Board lists fix-1 / ses_abc / fixer, call task with subagent_type: "fixer" and task_id: "fix-1" or task_id: "ses_abc"."

The task-session manager's handleToolExecuteBefore() (src/hooks/task-session-manager/tool-execute-hooks.ts) is expected to sanitize task_id before native execution: resolve reusable/recoverable aliases to the real ses_... id, delete unknown non-session values, or fail closed for running tasks.

What happened: On OpenCode 1.18.18 + plugin 2.2.14, a known non-ses_ Job Board value reached OpenCode's native SessionID.make() unchanged, even though Slim's tool.execute.before handler was invoked:

Expected a string starting with "ses", got "ora-1"

No child session was created.

Expected:

  • A reusable alias is rewritten to its real ses_... session ID.
  • A known but non-reusable alias is removed or rejected before native execution.
  • An unknown non-session value is removed or rejected before native execution.

Actual:
The native task tool received the original non-ses_ value and synchronously failed in SessionID.make(). In practice the model typically recovers on the next turn by retrying with the full ses_... id from the same Job Board entry, so the net impact is a wasted failed call plus an extra round-trip rather than a hard block — but the failed call is still avoidable and the alias guidance in the prompt remains misleading until fixed.

Steps to reproduce

  1. An orchestrator session (ses_ff2faaa54ffeXxLV33ZvaSxRB2) launched an oracle foreground task. The plugin registered it on the board:

    00:01:04.007 [task-session-manager] tentative early board registration from session.created
      {"taskID":"ses_ff2fa6e06ffe4srJHhVXGpCKlq","alias":"ora-1","parentSessionID":"ses_ff2faaa54ffeXxLV33ZvaSxRB2","agent":"oracle"}
    
    00:01:08.635 [task-session-manager] foreground task status registered
      {"taskID":"ses_ff2fa6e06ffe4srJHhVXGpCKlq","alias":"ora-1","parentSessionID":"ses_ff2faaa54ffeXxLV33ZvaSxRB2","agent":"oracle","state":"completed"}
    

    The task had completed but was not yet reconciled at this point.

  2. ~19 minutes later, resume with the alias as the prompt instructs:

    {
      "subagent_type": "oracle",
      "task_id": "ora-1",
      "description": "Analyze variant priority issue",
      "prompt": "..."
    }
  3. The before-hook did run for this call (plugin log oh-my-opencode-slim.20260816T235734.log):

    00:20:12.877 [task-session-manager] tool.execute.before task — pending call created
      {"callId":"call_4c791c298214471680422aea","parentSessionId":"ses_ff2faaa54ffeXxLV33ZvaSxRB2","agentType":"oracle","label":"Analyze variant priority issue","inputCallID":"call_4c791c298214471680422aea","inputSessionID":"ses_ff2faaa54ffeXxLV33ZvaSxRB2"}
    

    The pending-call log does not currently include resumedTaskId or the final task arguments, so its absence cannot prove which task-ID branch was selected. The native error is the authoritative evidence that the value eventually seen by OpenCode was still ora-1.

  4. The native tool call failed:

    Expected a string starting with "ses", got "ora-1"
    
  5. Control — 32 seconds later, passing the full ses_ id from the same board entry succeeded:

    00:20:44.325 [task-session-manager] tool.execute.before task — pending call created
      {"callId":"call_2acbb0d4f6b54373a05e9d4e",...,"agentType":"oracle","label":"Analyze variant priority issue",...}
    

    The session resumed successfully. This control confirms that the target session was resumable and that the native task tool succeeds when given the canonical session ID. It does not by itself identify whether the alias-resolution branch or the deletion branch failed.

Analysis:

At the time of the failing call (00:20:12), the task had completed but had not yet been reconciled — that transition happened at 00:35:18, 15 minutes after the failed call. In Slim's current implementation, resolveReusable() requires a completed terminal record with terminalUnreconciled === false and a context size within the configured limit. Therefore this reproduction primarily tests whether a known but non-reusable alias is safely removed or rejected before native execution. A separate post-reconciliation alias-resume test is needed to confirm a reusable-alias resolution failure.

In handleToolExecuteBefore(), every branch that handles a non-empty task_id either rewrites args.task_id, deletes it, keeps it only when it matches SESSION_ID_PATTERN (/^ses_[A-Za-z0-9_-]+$/), or throws for running tasks. None leaves "ora-1" in args.task_id.

OpenCode 1.18.18's v1 execution path passes the same args object through tool.execute.before and then to the native tool executor. Therefore, if handleToolExecuteBefore() actually deleted or rewrote args.task_id, the native executor should not see the original alias.

The observed behavior is inconsistent with that expected flow and strongly suggests that the mutation did not affect the arguments used by native execution. However, the current pending-call log does not prove that the delete/rewrite branch was executed. Additional branch-level and argument before/after logging is needed to distinguish these possibilities:

  • the handler selected the wrong board branch;
  • the argument was mutated but later restored;
  • the host passed a different argument object to native execution;
  • another hook restored the original value.

Suggested fix:

  1. Add branch-level logging (requested task_id, args before/after, branch selected, resolved task ID, board state, terminalUnreconciled) to confirm whether the host propagates args mutations.
  2. Stop relying on silent deletion; throw with a recoverable message before native execution when task_id is a non-ses_ value that cannot be safely transformed. Include the canonical ID only when the board has actually resolved the value.
  3. Prompt mitigation (temporary): require task_id to be the full ses_... id; update the example accordingly.
  4. Regression test matrix at the native execution boundary — invariant: a non-ses_ value must never reach SessionID.make().

Upstream context: anomalyco/opencode#37440 (same native failure mode), #37438 (open fix PR — would prevent the error but silently create a fresh session instead of resuming), #29964 (ses prefix constraint is long-standing).

Related history: #390 (introduced resumable aliases), #876 (hardened active-task reuse), commit 61a6603c (retained alias guidance).

oh-my-opencode.json

// Not applicable — this bug is in the task-session-manager hook logic,
// not in user configuration. The relevant config fields
// (backgroundJobs.maxSessionsPerAgent, maxContextLines) use defaults.

OpenCode version

1.18.18

oh-my-opencode-slim version

2.2.14

Operating system

macOS

Logs, screenshots, or extra context

# Plugin log: oh-my-opencode-slim.20260816T235734.log
# (v1 instance, directory=/Users/ybw0014/.config/opencode)
# 9 "pending call created" entries, matching the 9 task calls in this conversation.

00:01:04.007 [task-session-manager] tentative early board registration from session.created
  {"taskID":"ses_ff2fa6e06ffe4srJHhVXGpCKlq","alias":"ora-1","parentSessionID":"ses_ff2faaa54ffeXxLV33ZvaSxRB2","agent":"oracle"}

00:01:08.635 [task-session-manager] foreground task status registered
  {"taskID":"ses_ff2fa6e06ffe4srJHhVXGpCKlq","alias":"ora-1","parentSessionID":"ses_ff2faaa54ffeXxLV33ZvaSxRB2","agent":"oracle","state":"completed"}

00:20:12.877 [task-session-manager] tool.execute.before task — pending call created
  {"callId":"call_4c791c298214471680422aea","parentSessionId":"ses_ff2faaa54ffeXxLV33ZvaSxRB2","agentType":"oracle","label":"Analyze variant priority issue","inputCallID":"call_4c791c298214471680422aea","inputSessionID":"ses_ff2faaa54ffeXxLV33ZvaSxRB2"}
  # ← failing call (task_id="ora-1"), no resumedTaskId in log

00:20:44.325 [task-session-manager] tool.execute.before task — pending call created
  {"callId":"call_2acbb0d4f6b54373a05e9d4e",...,"agentType":"oracle","label":"Analyze variant priority issue",...}
  # ← control call (task_id="ses_ff2fa6e06ffe4srJHhVXGpCKlq"), succeeded

00:35:18.808 [task-session-manager] foreground task status registered
  {"taskID":"ses_ff2fa6e06ffe4srJHhVXGpCKlq","alias":"ora-1","parentSessionID":"ses_ff2faaa54ffeXxLV33ZvaSxRB2","agent":"oracle","state":"reconciled"}
  # ← reconciled transition, 15 min after the failed call

# Native error (returned as task_error to the orchestrator):
Expected a string starting with "ses", got "ora-1"

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions