Skip to content

fix: preserve OpenCode watcher continuity failure diagnostics - #2

Merged
mattleus merged 5 commits into
mainfrom
fm/fm-watcher-outcome-notify
Oct 8, 2026
Merged

mattleus merged 5 commits into
mainfrom
fm/fm-watcher-outcome-notify

Conversation

@mattleus

@mattleus mattleus commented Oct 7, 2026

Copy link
Copy Markdown
Owner

Intent

Preserve OpenCode watcher outcome diagnostics: the fm-primary-watch-arm OpenCode plugin was dropping watcher outcome diagnostic details; keep them so watcher outcomes carry the diagnostics needed downstream.

What Changed

  • The fm-primary-watch-arm OpenCode plugin now publishes a durable check: OpenCode watcher continuity failure queue row for every previously lost outcome detail: arm failures, undeliverable wake prompts, exhausted successor restorations, and plugin initialization failures (previously swallowed in empty catch blocks).
  • Deduped via hashed state markers with a new FM_WATCH_DIAGNOSTIC_DEDUPE_MS window (default 300000): identical diagnostics publish one row per window, and stale markers are expired or swept on the next publication so recurring failures surface again.
  • Added tests/fm-opencode-primary-watch-arm.test.sh pinning the durable-diagnostic contract against the real plugin, and documented the new behavior and knob in docs/watcher-continuity.md and docs/configuration.md.

Risk Assessment

✅ Low: The change is confined to failure-path diagnostic publication with dedupe bounded by a TTL, verified against the wake-queue library's field-sanitizing and locking contract, covered by behavioral tests, and matching its documented contract; no reachable defect could be constructed.

Testing

Drove the real fm-primary-watch-arm plugin through its OpenCode host contract against disposable fixture repos/homes: an undeliverable wake still persisted both the wake-failure and prompt-delivery-failure rows durably via the real fm-wake-lib.sh queue, a refused event stream persisted an initialization-failure row, an unrestorable successor persisted its retry-exhaustion detail, and an identical diagnostic republished only after its dedupe marker expired while the next publication swept other stale markers. The same suite run against the base-commit plugin reproduced the reported bug (no durable row at all). All drives were live against the real plugin module; a full real-OpenCode-CLI primary was not used because the failing OpenCode-side calls (prompt rejection, event-stream exception) cannot be induced deterministically inside a logged-in session without faking the host contract the plugin consumes, and the diff does not touch plugin loading.

  • Live validation: ✅ go - 5 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Undeliverable wake (OpenCode prompt API rejects) still leaves durable diagnostics: one debounced wake-failure row and one prompt-delivery-failure row, each exactly once, persisted in the home's .wake-… ✅ pass live queue-rows-live.txt artifact plus bash tests/fm-opencode-primary-watch-arm.test.sh scenario 'OpenCode prompt failure publishes durable diagnostics' (target-plugin-pass.txt)
Plugin initialization failure (OpenCode event stream throws) publishes one durable 'plugin initialization failed' queue row instead of being swallowed ✅ pass live bash tests/fm-opencode-primary-watch-arm.test.sh scenario 'OpenCode plugin initialization failure publishes a durable diagnostic' (target-plugin-pass.txt)
Unrestorable watcher successor (launch retries exhausted) surfaces its retry detail durably as a 'could not restore watcher continuity' queue row even though the wake prompt path alone would lose it ✅ pass live bash tests/fm-opencode-primary-watch-arm.test.sh scenario 'OpenCode missing successor surfaces a durable terminal failure' (target-plugin-pass.txt)
Identical diagnostics are deduped within the FM_WATCH_DIAGNOSTIC_DEDUPE_MS window (no duplicate rows), a recurring identical failure republishes after its marker expires, and publishing sweeps other s… ✅ pass live bash tests/fm-opencode-primary-watch-arm.test.sh scenario 'OpenCode dedupe marker expiry republishes an identical diagnostic' (target-plugin-pass.txt)
Adversarial regression check: the pre-change (base commit 687d414) plugin under identical failing conditions drops the diagnostics entirely, leaving an empty wake queue — the new suite fails against i… ✅ pass live base-plugin-regression.txt artifact ('not ok - OpenCode prompt failures must publish durable diagnostics: missing durable prompt failure' with empty queue against the base plugin, restored afterward)
Evidence: Target-commit suite run (four durable-diagnostic scenarios passing)

Source: Target-commit suite run (four durable-diagnostic scenarios passing)

ok - OpenCode prompt failure publishes durable diagnostics
ok - OpenCode plugin initialization failure publishes a durable diagnostic
ok - OpenCode missing successor surfaces a durable terminal failure
ok - OpenCode dedupe marker expiry republishes an identical diagnostic
Evidence: Regression reproduction against base-commit plugin (diagnostics dropped: 'missing durable prompt failure' with empty queue)

Source: Regression reproduction against base-commit plugin (diagnostics dropped: 'missing durable prompt failure' with empty queue)

not ok - OpenCode prompt failures must publish durable diagnostics: file://~/.no-mistakes/worktrees/e1a436203903/01M4C3TFF7JZWJH2AV0MVPTEJF/[eval1]:23
if (!queue.includes("OpenCode watcher continuity failure")) throw new Error(`missing durable prompt failure: ${queue}`);
                                                                  ^

Error: missing durable prompt failure: 
    at file://~/.no-mistakes/worktrees/e1a436203903/01M4C3TFF7JZWJH2AV0MVPTEJF/[eval1]:23:67

Node.js v25.4.0: expected exit 0, got 1
Evidence: Live drive evidence: persisted .wake-queue rows and .opencode-watch-diagnostic-* marker files from the real plugin plus real fm-wake-lib.sh

Source: Live drive evidence: persisted .wake-queue rows and .opencode-watch-diagnostic-* marker files from the real plugin plus real fm-wake-lib.sh

--- durable wake queue rows persisted by real bin/fm-wake-lib.sh --- 1791408492 1 check opencode-watch:cc2850cc53d73c373cb31980 check: OpenCode watcher continuity failure: watcher: FAILED - OpenCode could not deliver an actionable wake Cannot read properties of undefined (reading 'prompt') 1791408492 2 check opencode-watch:2edbd265a0c9e762d74207b7 check: OpenCode watcher continuity failure: prompt delivery failed: Cannot read properties of undefined (reading 'prompt') --- dedupe marker files in state dir --- .opencode-watch-diagnostic-2edbd265a0c9e762d74207b7 .opencode-watch-diagnostic-cc2850cc53d73c373cb31980

--- durable wake queue rows persisted by real bin/fm-wake-lib.sh ---
1791408492	1	check	opencode-watch:cc2850cc53d73c373cb31980	check: OpenCode watcher continuity failure: watcher: FAILED - OpenCode could not deliver an actionable wake Cannot read properties of undefined (reading 'prompt')
1791408492	2	check	opencode-watch:2edbd265a0c9e762d74207b7	check: OpenCode watcher continuity failure: prompt delivery failed: Cannot read properties of undefined (reading 'prompt')
--- dedupe marker files in state dir ---
.opencode-watch-diagnostic-2edbd265a0c9e762d74207b7
.opencode-watch-diagnostic-cc2850cc53d73c373cb31980
--- marker contents ---
== .opencode-watch-diagnostic-2edbd265a0c9e762d74207b7
check: OpenCode watcher continuity failure: prompt delivery failed: Cannot read properties of undefined (reading 'prompt')
== .opencode-watch-diagnostic-cc2850cc53d73c373cb31980
check: OpenCode watcher continuity failure: watcher: FAILED - OpenCode could not deliver an actionable wake
Cannot read properties of undefined (reading 'prompt')

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 5 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Undeliverable wake (OpenCode prompt API rejects) still leaves durable diagnostics: one debounced wake-failure row and one prompt-delivery-failure row, each exactly once, persisted in the home's .wake-… ✅ pass live queue-rows-live.txt artifact plus bash tests/fm-opencode-primary-watch-arm.test.sh scenario 'OpenCode prompt failure publishes durable diagnostics' (target-plugin-pass.txt)
Plugin initialization failure (OpenCode event stream throws) publishes one durable 'plugin initialization failed' queue row instead of being swallowed ✅ pass live bash tests/fm-opencode-primary-watch-arm.test.sh scenario 'OpenCode plugin initialization failure publishes a durable diagnostic' (target-plugin-pass.txt)
Unrestorable watcher successor (launch retries exhausted) surfaces its retry detail durably as a 'could not restore watcher continuity' queue row even though the wake prompt path alone would lose it ✅ pass live bash tests/fm-opencode-primary-watch-arm.test.sh scenario 'OpenCode missing successor surfaces a durable terminal failure' (target-plugin-pass.txt)
Identical diagnostics are deduped within the FM_WATCH_DIAGNOSTIC_DEDUPE_MS window (no duplicate rows), a recurring identical failure republishes after its marker expires, and publishing sweeps other s… ✅ pass live bash tests/fm-opencode-primary-watch-arm.test.sh scenario 'OpenCode dedupe marker expiry republishes an identical diagnostic' (target-plugin-pass.txt)
Adversarial regression check: the pre-change (base commit 687d414) plugin under identical failing conditions drops the diagnostics entirely, leaving an empty wake queue — the new suite fails against i… ✅ pass live base-plugin-regression.txt artifact ('not ok - OpenCode prompt failures must publish durable diagnostics: missing durable prompt failure' with empty queue against the base plugin, restored afterward)
  • bash tests/fm-opencode-primary-watch-arm.test.sh at target commit b4a8ea13 (all four scenarios pass)
  • bash tests/fm-opencode-primary-watch-arm.test.sh with .opencode/plugins/fm-primary-watch-arm.js temporarily reverted to base commit 687d414 (regression reproduced: 'missing durable prompt failure' with an empty wake queue), then restored with git checkout --
  • Bespoke live drive: real plugin setup() in node with a fixture OpenCode host (session.promptAsync always rejecting), real fixture branch of bin/fm-watch-arm.sh, real bin/fm-wake-lib.sh appending into a disposable FM_HOME; dumped the persisted .wake-queue rows and .opencode-watch-diagnostic-* markers as reviewer-visible evidence
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@mattleus
mattleus merged commit a2a4711 into main Oct 8, 2026
11 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants