Skip to content

opencode-claimant: requeue/lease expiry never clears a bound session_id — a live session can sit on a claimable task (7787), and a relaunch can never re-bind #5001

Closed

Description

@dezren39

Summary

A task can be requeued while a claimant-bound session is still live or while a
dead binding still exists
, and nothing clears progress.session_id:

  1. Launch race (live, task 7787). The claim lease expired 2026-11-03T15:31:38.44Z.
    The 09:20 CT claimant fire — using a poll snapshot taken before expiry —
    created and bound session ses_e5e5df699f1ffeTb5Z3gWDNsYI for it at
    15:31:40.62Z, two seconds after expiry. The task row is now
    status=queued, worker_id=null, lease_until=null, while that session is
    LIVE in GET /api/session/active and executing the task.
  2. Stale bindings on requeued rows. Of 36 queued tasks that still carry a
    bound session_id, 33 have a terminal session, 2 a marker-less stale one, and
    1 (7787) a live one. The claim was released; the binding was not.

Impact

  • Double execution. A queued task is claimable by any lane. For 7787 the
    next claim executes the same issue alongside the live session. The atomic bind
    in migration 0021 cannot prevent this: binding runs at launch, and by then
    the task is no longer claimed, so bind raises no conflict at all.
  • Orphan sessions that can never bind. bind_repo_task_lane_session refuses
    to overwrite a different existing binding. So a relaunch of any of the 35
    dead-session queued rows would create a session whose bind is rejected —
    exactly the Oct-28 unbound-launch defect Move opencode executions to laptop-side scheduled tasks — retire the Muse boot handoff #3026 exists to close, reached by the
    opposite path.
  • The claimant's step 4a re-verify does not catch facet 1 unless it re-reads the
    ledger in the same instant as the create; the lease can die between poll and
    POST /api/session.

Reproduction / current state

Read-only snapshot from the 2026-11-03 09:30 CT claimant fire
(~/.local/state/opencode-claimant/fires.jsonl):

task 7787 | repo-health-code-pilot | status=queued | stage=launched
          | session_id=ses_e5e5df699f1ffeTb5Z3gWDNsYI
          | launched_by=opencode-claimant | worker_id=null | lease=null
          | stage_at=2026-11-03T15:31:40.617952Z | attempts=3
session ses_e5e5df699f1ffeTb5Z3gWDNsYI -> in GET /api/session/active (LIVE)

Ledger query for facet 2:

GET tasks?status=eq.queued&progress->>session_id=not.is.null   -> 36 rows
per-id session read: terminal 33 | markerless-stale 2 | LIVE 1

State at fire close (~15:40Z)

The bound session noticed its own claim had lapsed and re-claimed the task
itself
(status=claimed, worker_id=repo-worker-code, stage verified), so
no second executor appeared and no duplicate work occurred. But that repair was
ad-hoc, taken by a worker whose prompt said the task was "already claimed", and
it was only possible because a worker may claim. For ~6.5 minutes (15:31:38 →
~15:38)
the task was queued and claimable by any lane while a live session
was already executing it. Avoid the exposure window, not the repair.

Suggested direction (not prescriptive)

  • Requeue/reap must disposition the binding, not just the claim: clear
    progress.session_id + launched_by when the bound session is terminal and
    the task returns to queued, record the old id in history, and leave bind
    free to overwrite.
  • Claimant launch must re-assert the claim under the launch, e.g. heartbeat
    or claim-guard the row in the same transaction/step as create+bind, and treat
    "lease lost between poll and create" as a skip (no session created).
  • A queued task with a live bound session should be unclaimable (claim should
    refuse, or the row should carry a bound_live guard).
  • Task 7787's live session needs a decision from the ledger backstop: adopt it
    (re-claim to the worker) or let it finish without any lane claiming the row.

Scope note

Found during supervision of #3026 slice 1; the claimant correctly launched and
mutated nothing this fire (cap saturated, 14 active roots vs cap 12). Related:
#4757 (session-list truncation), #4758 (bound session with no prompt, stuck
claimed), #4912 (session contract design), #3026 (slice 1).

Acceptance criteria

  • When a task is requeued (lease expiry, park expiry, or lane release), any progress.session_id bound to a previous claim is cleared or superseded in the same transition, so a queued row never carries a live or stale binding.
  • A relaunch of a previously bound task can bind its new session (the 0021 bind no longer refuses because of a dead predecessor binding).
  • The launch race is closed: a session created from a pre-expiry poll snapshot cannot end up executing a task that has already been requeued — the claimant re-verifies the claim at bind time and aborts/rebinds on mismatch.
  • The 36 queued rows carrying bound session_ids measured at filing are reconciled (bindings cleared or verified live-and-owned).

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    source/queue-workerFiled or worked by the queue/worker pilot
    priority/highImportant and time-sensitive
    app/pm-stackpm-stack (Pad/Grist) in developing-today/code
    effort/mMedium: expected under 1 day of work
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Nov 3, 2026
  2. added
    queue/queuedHas a queued task in the Supabase agent queue
    task/triageQueue packet kind: verify, reproduce, and route this item
    on Nov 3, 2026
  3. developing-today commented on Nov 3, 2026

    @developing-today
    Contributor

    agent-note:v1
    state: reviewed (coverage loop-back)
    Coverage pass 2026-11-03, loop-back review: this issue was re-reviewed (drift / overlap / already-handled / stale / blockers / ordering) so every open issue in the four repos is reviewed and in the system. Verdict: still live; in the system as task 8570 (triage, queued, priority 50) in repo-health-code-pilot. Labels audited and synced. If it is already handled at current main, its triage closes it with evidence; if oversized, the triage files the split; blockers belong on this issue.
    Found via: coverage loop-back 2026-11-03

  4. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    Intake review

    Fleet monitor intake (Duty 2), 2026-11-03. (Posted on a delayed retry: the original comment attempts were rejected by a GitHub secondary rate limit; the label/body enrichment itself landed during the run.)

    • Related: code#3026 (the unbound-launch defect this is the mirror image of), code#5016 (sibling claimant defect), migration 0021 (atomic bind).
    • Still live: filed from a read-only snapshot at 2026-11-03 09:30 CT with a live double-execution instance (task 7787's session bound 2s after lease expiry); nothing has landed since.
    • Notes: this is the same evaporation family the fleet monitor repairs hourly on the label side — here it is the session-binding side. Acceptance criteria appended from the body's three facets; effort m.
    • Risks: clearing bindings eagerly could unbind a legitimately resumed session; the clear must key on the claim transition, not on session liveness guesses.
    • Suggested approach: clear/supersede progress.session_id inside the requeue path itself (one writer, atomic with the status change), then add the bind-time claim re-verification.
  5. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed on Nov 4, 2026
  6. 35 remaining items

  7. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  8. added a commit that references this issue on Nov 4, 2026
  9. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 4, 2026
  10. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  11. added a commit that references this issue on Nov 4, 2026
  12. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

  13. removed
    task/triageQueue packet kind: verify, reproduce, and route this item
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedQueue worker lifecycle state
    on Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/pm-stackpm-stack (Pad/Grist) in developing-today/codeeffort/mMedium: expected under 1 day of workkind/bugSomething isn't working (prefixed form of the classic bug label)meta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/highImportant and time-sensitivesource/queue-workerFiled or worked by the queue/worker pilot

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions