anomalyco / anomalyco/opencode
[BUG]: Status.get() returns idle for a session absent from the status map
@neriousy is already working on this.
Since Aug 17, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- Avg merge
- 7h 2m
- Merged PRs (30d)
- 384
Description
Describe the bug
packages/opencode/src/session/status.ts — set(sessionID, status) publishes Event.Status on transition, and on type === "idle" publishes Event.Idle and deletes the map entry. get() then returns { type: "idle" } for any session with no entry, so "absent" and "idle" are indistinguishable to every consumer.
That makes idle a positive claim about a session that may not exist. The status map is InstanceState.make(… new Map()) — per-process and non-durable — so after a host restart every session reads idle, including sessions that were in-flight when the process died. Any consumer gating completion, cleanup, or notification on idle sees a fleet-wide false quiesce at exactly the moment the truth is unknown. The failure is silent and reads as health.
Worth documenting alongside: idle fires on all termination shapes — clean, error, and interrupt — so it is a quiesce barrier, not a success signal. The absent/idle conflation invites reading it as success.
Expected behavior
Absent and idle should be distinguishable — get() returning undefined (or an explicit unknown) for a missing entry, with idle reserved for an observed transition. Consumers can then treat unknown as "must check durably" rather than "finished".
The durable side already carries the discriminator: message.data.time.completed, set at packages/opencode/src/session/processor.ts:595, is persisted and survives restart — present on 49,961 of 50,000 sampled assistant messages on a live database. A session whose latest assistant message lacks it was in-flight when the process died. The information exists; the status surface collapses it.
For contrast within the same area, the field validation in packages/opencode/src/plugin/shared.ts throws precisely on every malformed input rather than returning a default that reads as success.
Steps to reproduce
- Start a session and let it go idle, or start one and kill the process mid-turn.
- Call the status surface for a session ID that has no map entry (any session after a restart, or any session that has already gone idle once).
- Observe
{ type: "idle" }returned for a session the process has no state for.
Note: these steps are derived from the mechanism rather than from a recorded run — the finding came from reading the code path.
Environment
- Version: reproduces on current
dev - Platform: Linux (not platform-specific — the mechanism is in-memory state plus a defaulted read)
Additional context
Found while integrating an external supervisor process that consumes opencode session status to decide when a background task has finished. It surfaced there as a false-completion vector: the supervisor's candidate check reads idle, and on a host restart every session presents as idle at once.
A consumer can defend against this by treating "absent from the status map" as a distinct unknown state rather than as idle, but only if it already assumes the surface is lossy. A consumer taking get() at face value has no way to tell the two apart.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.