Skip to content

Thread wedges at StartingExecution forever when the _Exec commit is lost (e.g. NodeType release recycles the hub mid-claim) — no recovery on activation #539

Closed

Description

@rosalie1984

Symptom

A thread round submitted via hub.StartThread(...) can wedge at Status = StartingExecution forever: the user message stays in pendingUserMessages, no cell is allocated, no agent round runs, and nothing ever recovers it. The thread only executes when its hub is explicitly re-activated (e.g. a DisposeRequest recycle of the main node followed by an access).

Production evidence (memex portal, UW Deepfield Industry News auto-update)

Two gather rounds (agent Assistant, model DeepSeek-V4-Flash, submitted through the canonical hub.StartThread surface from a layout-area click):

Thread (under UWDeepfield/IndustryNews/_Thread/) Submitted Wedged state Recovered
gather-…-2958 2026-07-16 05:27:46 StartingExecution, message in pendingUserMessages, thread version 1 — for 2.5 days 2026-07-18 ~21:50, within seconds of an explicit recycle of UWDeepfield/IndustryNews + next access; thread ran to completion (v1 → v90), items landed correctly
gather-…-b0f2 2026-07-18 21:41:11 same same moment, same way (v1 → v90)

Trigger correlation — both wedged rounds were submitted ~60–70 s after a NodeType compile + release recycled the main-node hub:

  • Release 20260716052635 at 05:26:35 → thread submitted 05:27:46 → wedged.
  • Release 20260718214041 at 21:40:41 → thread submitted 21:41:11 → wedged.

(Opening the feed triggers the stale-assembly recompile/release, which recycles the per-node hub; the user's toggle click then lands right on that boundary.)

The model was exonerated: both rounds executed quickly and correctly on DeepSeek-V4-Flash once the hub was re-activated.

Root cause

The round dispatch is a two-edge handshake:

  1. Claim — InstallSubmissionWatcher (on the thread hub's action block) writes Idle → StartingExecution.
  2. Commit — the _Exec round watcher's first emission commits StartingExecution → Executing and launches the round.

When the commit edge is lost — here: the _Exec startup raced the release-recycle of the freshly claimed thread's hub environment — the claim survives as durable node state but no recovery path exists. InitializeThreadLifecycle (src/MeshWeaver.AI/ThreadExecution.cs) self-heals every other state on activation:

  • Executing + ActiveMessageId → resume the interrupted round;
  • Executing without a cell → reset to Idle so the submission watcher re-claims;
  • Idle / Cancelled + pending → submission watcher claims;
  • StartingExecution → deliberately no write: "the _Exec round watcher fires on its own first emission."

That assumption doesn't hold when the claim was made by a previous hub incarnation (or _Exec never came up): nothing activates _Exec, so the "first emission" never happens, and the thread is parked in a state that ThreadStateTransitions treats as in-flight — resubmission is refused, the submission watcher won't re-claim. The cancel path already acknowledges this exact window (the "No-CTS claim-window fallback" writes terminal Cancelled directly when Status is stuck at StartingExecution), but normal resumption has no equivalent.

Needed fix (root cause — no timers/watchdogs)

On thread-hub activation, StartingExecution must not be a no-op. Activation is proof that no round is actually in flight (executions die with their hub), so the recovery can act deterministically, mirroring the existing Executing branches:

  • either drive the commit: ensure the _Exec hub is started and its round watcher observes the claimed state (so the claim → commit → launch chain completes), or
  • roll the orphaned claim back (CAS-guarded, same idempotent shape as the Executing-without-cell reset): StartingExecution → Idle, letting InstallSubmissionWatcher re-claim the still-queued pendingUserMessages.

Care needed to not reintroduce the re-dispatch ping-pong documented in ThreadStateTransitions (Executing → StartingExecution stays illegal; this is only about an orphaned claim on a freshly activated hub, before any commit).

Deterministic repro to pin the fix (per WritingTests.md): submit via StartThread, dispose the hub after the claim write lands but before _Exec commits, re-activate the hub, assert the round executes and pendingUserMessages drains — observed via GetMeshNodeStream(threadPath).Where(...).FirstAsync().Timeout(...).

Impact

Any StartThread submission racing a hub recycle (NodeType release, redeploy, grain migration) silently never runs; from the product side it reads as "the agent ignored me". In UW Deepfield the feed's 6-minute stale-guard clears the UI, so the failure is doubly silent — the round just never happens. A 2.5-day wedge was observed in production with the page being reopened repeatedly in between; only an explicit recycle recovered it.

Pointers

  • src/MeshWeaver.AI/ThreadExecution.cs — InitializeThreadLifecycle recovery branch table (the StartingExecution no-op), InstallSubmissionWatcher, _Exec round-watcher commit edge, No-CTS claim-window cancel fallback.
  • src/MeshWeaver.AI/ThreadStateTransitions.cs — legal edges; StartingExecution counts as in-flight, so nothing re-claims it.
  • Field diagnosis thread: memex UWDeepfield/IndustryNews auto-update, 2026-07-18/19.

Activity

  1. rosalie1984 commented on Jul 18, 2026

    @rosalie1984
    CollaboratorAuthor

    Update — the wedge is NOT a release-window race; on the distributed portal the commit edge never fires at all

    New evidence from three further rounds (same feed, same StartThread click path, submitted 2026-07-18 21:58:08, 22:01:11, 22:01:36 UTC):

    • No release / recompile anywhere near them (last release was 21:49:54), the main-node hub had been recycled and re-accessed minutes earlier — and all three still wedged at StartingExecution with the message in pendingUserMessages, thread version 1.
    • So the original release-window correlation was incidental. On the deployed distributed portal (memex, Orleans), every round submitted through this path wedges: the claim (Idle → StartingExecution) is written, and the _Exec round watcher's commit never fires spontaneously. Not one of the ~10 rounds observed since 2026-07-15 ever executed without an external nudge.

    What does and doesn't recover a wedged round (all verified live):

    Action Result
    Reading the thread node (get) nothing
    Recycling the main node (DisposeRequest) alone nothing
    Recompile + new release alone nothing
    Any write addressed to the wedged thread node (a trivial metadata patch) hub activates, recovery drains the round within seconds — thread went v2 → v63/v87, round completed correctly
    Recycle + a real portal-side access (open Blazor page) same recovery (this is what happened in the original report)

    The ~11-minute gap between submission and the successful poke is consistent with the thread hub having idled out and the write forcing a fresh activation, i.e. recovery-on-activation works; the spontaneous claim→commit handoff is what's broken. (A delayed self-nudge from application code would be a watchdog band-aid and is not an acceptable interim.)

    Sharpened fix requirements:

    1. The claim→commit edge must not depend on a hub/grain that nothing guarantees is running. When InstallSubmissionWatcher writes the claim, the _Exec execution hub must be deterministically started/driven as part of the same dispatch — or the commit must happen on the thread hub itself.
    2. Activation recovery must handle StartingExecution (as originally filed): on thread-hub activation, an orphaned claim with queued pendingUserMessages must be rolled forward (drive _Exec) or back (→ Idle, CAS-guarded) — activation proves nothing is in flight.

    Repro guidance stands, but the deterministic test should cover the distributed case: claim written, _Exec never activated, thread hub deactivates; assert (a) a fresh dispatch commits without external writes, and (b) re-activation drains an orphaned claim.

    Live impact update: UW Deepfield's Industry News auto-update is effectively dead on the portal — every toggle-triggered gather wedges silently and only runs when something else happens to write to the thread node.

  2. added a commit that references this issue on Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    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