Skip to content

Concurrency: unlocked state.json races (cancel/status/task-completion) #4

Description

@TheRealDinghyDog

Concurrency: unlocked state.json races (OpenCode adversarial review of PR #1)

Surfaced by an OpenCode adversarial self-review, triaged by Claude Code. Flagged as the highest-risk cluster: correctness of every concurrent operation (cancel, status, background-task completion) depends on these. Phase 2 added a server lock but there is no state-file lock.

4. lib/state.mjssaveState load-then-write, no serialization

Reads previousJobs from disk, computes nextJobs from in-memory state, deletes orphaned files, writes new state. Two concurrent saveState calls → last-writer-wins: one writer's deletions can remove files for jobs the other still tracks.

6. opencode-companion.mjs handleCancel vs. tracked-jobs.mjs runTrackedJob

Both write job status unlocked. If cancel writes last, a successfully completed task is overwritten as cancelled and its result is lost; if the worker writes last, the cancel is lost. (High impact — silent data loss.)

7. lib/state.mjsupsertJobupdateStatesaveState unlocked

Every upsertJob (progress updater, completion, cancel, enqueue) is load-mutate-save with no lock. The main-process progress reporter, the task-worker process, and a cancel from another hook all race on state.json; one process's mutations are silently discarded by another's writeFileSync.

8. lib/server-lifecycle.mjsensureServer usage window unprotected

After ensureServer releases its lock, nothing prevents teardownServerSession (e.g. from session-lifecycle-hook.mjs) from disposing the server while a caller is mid-request. The startup-path guards don't cover the usage window → broken connection / connection refused with no retry.

Suggested direction

Introduce a single-writer discipline for state.json (file lock / atomic compare-and-swap / serialize through one owner), and a usage refcount or lease so the server isn't torn down mid-request.

Ref: PR #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