Skip to content

opencode run teardown race after session.idle can create empty continuation assistant turn ("parts": []) #15267

Description

@twm-dynamo

Summary

In non-interactive opencode run, ULW/Ralph continuation can be injected after session.idle while run teardown/disposal is already starting. The second loop turn may be created with an empty assistant payload ("parts": []).

This is reproducible with /ulw-loop ... --max-iterations=2 under both --strategy=continue and --strategy=reset.

Environment

  • OpenCode CLI: 1.2.14
  • oh-my-opencode plugin: 3.8.5
  • OS: Linux

Reproduction

opencode run "/ulw-loop Write TRACE_TWICE and do not promise completion --max-iterations=2"
opencode run "/ulw-loop Write TRACE_TWICE_RESET and do not promise completion --max-iterations=2 --strategy=reset"

Then inspect the session:

opencode session list --format json -n 3
opencode export <session_id> > /tmp/<session_id>.json

Expected

  • If continuation is injected, the continuation assistant turn should contain a non-empty response payload.
  • If run mode is intentionally single-pass, continuation should not be injected.

Actual

  • Continuation directive is injected for loop 2.
  • A follow-up assistant message exists but has empty content ("parts": []).

Evidence

Exported sessions (directive present + empty assistant payload)

  • /tmp/ses_368d1cd6affeagO950PZdu6tid.json:279
  • /tmp/ses_368d1cd6affeagO950PZdu6tid.json:314
  • /tmp/ses_368d192b2ffeyCZLYWUS8MUyo8.json:295
  • /tmp/ses_368d192b2ffeyCZLYWUS8MUyo8.json:330

Trace ordering (idle/dispose timing overlaps continuation enqueue)

  • /tmp/ulw_trace_run.log:360
  • /tmp/ulw_trace_run.log:364
  • /tmp/ulw_trace_run.log:365
  • /tmp/ulw_trace_run.log:382
  • /tmp/ulw_trace_run.log:501

Related runtime signal during continuation startup

  • /tmp/ulw_trace_run.log:487

Working hypothesis

session.idle continuation enqueue and run-mode disposal are not serialized, allowing continuation to be posted during teardown and resulting in a partial second turn.

Local mitigation (plugin-side, not core fix)

Continuation hooks were disabled in run mode in local plugin bundle:

  • /home/wagner/.config/opencode/node_modules/oh-my-opencode/dist/index.js:58415
  • /home/wagner/.config/opencode/node_modules/oh-my-opencode/dist/index.js:58488
  • /home/wagner/.config/opencode/node_modules/oh-my-opencode/dist/index.js:58808
  • /home/wagner/.config/opencode/node_modules/oh-my-opencode/dist/index.js:58812
  • /home/wagner/.config/opencode/node_modules/oh-my-opencode/dist/index.js:58838

This avoids partial loop behavior in run mode but does not address runtime sequencing.

Requested core fix

  1. Serialize run teardown with post-idle continuation enqueue.
  2. If continuation is posted, defer disposal until that turn reaches a terminal state (completed/failed/rejected).
  3. If run mode disallows continuation, reject/skip enqueue before posting the directive.

Acceptance criteria (testable)

  1. Running /ulw-loop ... --max-iterations=2 in opencode run never produces continuation assistant messages with "parts": [].
  2. Behavior is deterministic in both --strategy=continue and --strategy=reset.
  3. Exactly one mode is enforced per run:
    • continuation allowed: directive is injected and continuation response is non-empty, or
    • continuation disallowed: no continuation directive is injected.
  4. A regression test covers the idle->dispose sequencing case where continuation posting and teardown are concurrent candidates.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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