@exaudeus/workrail 3.59.3 → 3.59.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,93 @@
1
+ # Design Review: In-Process awaitSessions and getAgentResult
2
+
3
+ **Date:** 2026-04-19
4
+ **Candidate reviewed:** Candidate A from `coordinator-in-process-await-candidates.md`
5
+
6
+ ---
7
+
8
+ ## Tradeoff Review
9
+
10
+ **Tradeoff: `port` field made optional in `CoordinatorDeps`**
11
+ - Verified: `deps.port` is unused by all coordinator logic (grep returns zero results)
12
+ - Condition that invalidates: TypeScript errors showing `port` required elsewhere
13
+ - Mitigation: Run `npm run build` immediately; pivot to `port: 0` sentinel if errors appear
14
+ - **Verdict: Acceptable**
15
+
16
+ **Tradeoff: `null consoleService` fallback for missing `ctx.v2` ports**
17
+ - Verified: `createToolContext()` always provides non-null values in production
18
+ - Condition that invalidates: Not applicable in production path
19
+ - Mitigation: Warn on stderr + degrade gracefully to same all-failed behavior as current HTTP failure
20
+ - **Verdict: Acceptable**
21
+
22
+ **Tradeoff: Pending-Set polling over check-all-handles-every-poll**
23
+ - Verified: Terminal state transitions are monotonic (event log is append-only)
24
+ - Condition that invalidates: Not possible given ConsoleRunStatus projection semantics
25
+ - **Verdict: Correct and more efficient**
26
+
27
+ ---
28
+
29
+ ## Failure Mode Review
30
+
31
+ | Failure Mode | Handled? | Risk |
32
+ |---|---|---|
33
+ | `ctx.v2.dataDir`/`directoryListing` null | Yes -- null guard + graceful fallback | Low |
34
+ | Session not yet visible after spawnSession | Yes -- SESSION_LOAD_FAILED treated as retry | Low |
35
+ | TypeScript error from port optional | Partially -- pivot to sentinel if needed | Low |
36
+ | ConsoleService circular dependency | Yes -- dynamic import pattern | Low |
37
+ | getSessionDetail/getNodeDetail unexpected throw | Yes -- ResultAsync + outer try/catch | Low |
38
+
39
+ **Highest-risk**: TypeScript port optional change causing build failure. Pivot defined and trivial.
40
+
41
+ ---
42
+
43
+ ## Runner-Up / Simpler Alternative Review
44
+
45
+ **Candidate B** (port: 0 sentinel): Nothing worth borrowing. The only advantage was zero interface changes, but it introduces a dead required field with a misleading sentinel value.
46
+
47
+ **Simpler variant** (keep `port: DAEMON_CONSOLE_PORT`): Insufficient -- leaves dead code after removing the HTTP deps that needed it. Design doc explicitly lists port removal as required.
48
+
49
+ **No hybrid needed.**
50
+
51
+ ---
52
+
53
+ ## Philosophy Alignment
54
+
55
+ | Principle | Status |
56
+ |---|---|
57
+ | Architectural fixes over patches | SATISFIED -- root-cause fix, not workaround |
58
+ | Make illegal states unrepresentable | SATISFIED -- no sentinel, optional instead |
59
+ | Dependency injection for boundaries | SATISFIED -- ConsoleService gets ports injected |
60
+ | Immutability by default | SATISFIED -- minimal mutable state (pending Set only) |
61
+ | Errors are data | SATISFIED -- ResultAsync.isOk()/isErr() throughout |
62
+ | YAGNI with discipline | SATISFIED -- no speculative abstractions |
63
+ | Document "why", not "what" | REQUIRED -- add WHY comments in implementation |
64
+
65
+ One acceptable tension: null consoleService uses nullable variable rather than Result type. Acceptable at initialization boundary (not domain logic).
66
+
67
+ ---
68
+
69
+ ## Findings
70
+
71
+ **No Red (blocking) findings.**
72
+
73
+ **Orange (should address before shipping):**
74
+ - None identified.
75
+
76
+ **Yellow (advisory):**
77
+ - Y1: The `ctx.v2` null guard creates a nullable `consoleService` variable. If `ctx.v2` is ever null in production, the stderr warning may be missed. Recommend making the warning prominent: `[CRITICAL trigger-listener:reason=consoleService_unavailable]` prefix.
78
+
79
+ ---
80
+
81
+ ## Recommended Revisions
82
+
83
+ 1. Add `[CRITICAL]` prefix to the `ctx.v2` null guard warning to make it visible in logs.
84
+ 2. Add WHY comment on new `awaitSessions` explaining the in-process approach (mirrors the spawnSession WHY comment pattern).
85
+ 3. Add WHY comment on `ConsoleService` construction explaining it avoids the HTTP race condition.
86
+
87
+ ---
88
+
89
+ ## Residual Concerns
90
+
91
+ - **None blocking.** The design is sound, the tradeoffs are acceptable, and the failure modes are covered.
92
+ - Build verification (`npm run build`) will immediately catch any TypeScript issues from the `port` optional change.
93
+ - The implementation is a near-direct transcription of the design doc pseudocode, reducing creative risk.
@@ -0,0 +1,199 @@
1
+ # Coordinator I/O Error Handling -- Design Candidates
2
+
3
+ Generated: 2026-04-19
4
+
5
+ ## Problem Understanding
6
+
7
+ ### Core Tensions
8
+
9
+ 1. **Crash-safety vs. DI purity**: The coordinator declares "all phase failures produce
10
+ `PipelineOutcome { kind: 'escalated' }` -- never thrown" as a design invariant, but three
11
+ injected dep functions (`getAgentResult`, `postToOutbox`, `pollForPR`) are called without
12
+ try/catch in the mode files. Any throw from these functions crashes the coordinator silently
13
+ instead of returning a structured `PipelineOutcome`. The fix must enforce the invariant at
14
+ the right boundary.
15
+
16
+ 2. **Verbosity vs. DRY**: `postToOutbox` is called at 8+ critical escalation points across
17
+ `implement-shared.ts` and `full-pipeline.ts`. Each call site needs individual protection.
18
+ Inline try/catch at 8 sites is repetitive; a helper would reduce duplication but adds
19
+ abstraction not in the existing codebase pattern.
20
+
21
+ 3. **`process.stderr.write()` vs. `deps.stderr()`**: The prescribed pattern uses
22
+ `process.stderr.write()` in catch blocks, but the rest of the coordinator uses the injected
23
+ `deps.stderr()`. The tension is minor -- catch blocks represent unexpected I/O failures,
24
+ so using `process.stderr.write()` signals this is an emergency log path, not a normal
25
+ operational log.
26
+
27
+ ### Likely Seam
28
+
29
+ The mode files are the correct seam. `implement-shared.ts`, `full-pipeline.ts`, and
30
+ `implement.ts` are the callers of the three unsafe deps. The coordinator owns the
31
+ escalation-first invariant -- not the injectors (`trigger-listener.ts`, `cli-worktrain.ts`).
32
+
33
+ ### What Makes This Hard
34
+
35
+ - `postToOutbox` calls are immediately followed by `return { kind: 'escalated', ... }`. The
36
+ try/catch must wrap ONLY the `postToOutbox` call, not the return statement. Careful
37
+ placement required.
38
+ - `pollForPR` is called in BOTH `implement.ts` (explicitly mentioned in task) AND
39
+ `full-pipeline.ts` line 454 (not mentioned but equally unsafe). Both must be wrapped.
40
+ - UX gate zombie detection: `implement.ts` line 144 assigns `uxHandle` from `uxSpawnResult.value`
41
+ without a null/empty-string guard before passing to `awaitSessions`. This is the only session
42
+ handle in the coordinator without the guard -- a separate gap alongside the I/O error handling.
43
+
44
+ ---
45
+
46
+ ## Philosophy Constraints
47
+
48
+ **From `CLAUDE.md`:**
49
+ - "Errors are data -- represent failure as values (Result/Either), not exceptions as control flow"
50
+ - "Type safety as the first line of defense"
51
+
52
+ **From `adaptive-pipeline.ts` header (design invariant):**
53
+ - "All phase failures produce PipelineOutcome { kind: 'escalated' } -- never thrown."
54
+ - "All I/O is injected via AdaptiveCoordinatorDeps. Zero direct fs/fetch/exec imports."
55
+
56
+ **Repo precedent:**
57
+ - `archiveFile` (in `implement.ts` and `full-pipeline.ts`): try/catch inline in finally block, log-and-continue. This is the exact model for `postToOutbox`.
58
+ - `writeFile` routing log (in `adaptive-pipeline.ts`): try/catch inline, log-and-continue.
59
+
60
+ **Conflicts:** None material. The stated philosophy (errors as data) and the repo pattern (inline try/catch for non-Result deps) are consistent.
61
+
62
+ ---
63
+
64
+ ## Impact Surface
65
+
66
+ - `runReviewAndVerdictCycle` is called from both `implement.ts` and `full-pipeline.ts`. Fixing
67
+ `getAgentResult` in `implement-shared.ts` protects both callers automatically.
68
+ - `runAuditChain` (also in `implement-shared.ts`) calls both `getAgentResult` and `postToOutbox`
69
+ at multiple points.
70
+ - `adaptive-pipeline.ts` line 362 calls `postToOutbox` in the `ESCALATE` routing case -- this is
71
+ OUT OF SCOPE for this task (task restricts changes to the 3 mode files).
72
+ - No callers outside these files change signature or return type.
73
+
74
+ ---
75
+
76
+ ## Candidates
77
+
78
+ ### Candidate 1: Inline try/catch at each call site (prescribed pattern)
79
+
80
+ **Summary:** Wrap each `getAgentResult`, `postToOutbox`, and `pollForPR` call individually in
81
+ a try/catch block in the 3 mode files.
82
+
83
+ **Tensions resolved:** Crash-safety fully addressed. Accepts: slight verbosity from 8+
84
+ `postToOutbox` sites.
85
+
86
+ **Boundary:** At the mode file call sites -- the correct boundary. The coordinator owns the
87
+ escalation-first invariant; the mode files are where the invariant must be enforced.
88
+
89
+ **Failure mode:** Missing the `pollForPR` call in `full-pipeline.ts` (not explicitly called out
90
+ in task description but confirmed unsafe by code analysis). Must be systematic.
91
+
92
+ **Repo-pattern relationship:** Follows `archiveFile` try/catch pattern exactly. Adapts
93
+ `writeFile` routing-log pattern from `adaptive-pipeline.ts`.
94
+
95
+ **Gains:** Zero risk to happy path. Locally visible -- reviewer can see exactly what is
96
+ protected at each call site. No new abstractions.
97
+
98
+ **Losses:** Mildly repetitive for `postToOutbox` sites. Functions grow slightly.
99
+
100
+ **Scope:** Best-fit. The 3 files are exactly the seam.
101
+
102
+ **Philosophy fit:** Honors "errors are data", "escalation-first invariant", "DI for boundaries".
103
+ Minor: uses `process.stderr.write()` in catch blocks rather than `deps.stderr()`, consistent
104
+ with prescribed pattern and emergency-log semantics.
105
+
106
+ ---
107
+
108
+ ### Candidate 2: Wrap at injection site (safe wrapper functions)
109
+
110
+ **Summary:** Wrap `getAgentResult`, `postToOutbox`, `pollForPR` in safe adapter functions at
111
+ the injection sites (`trigger-listener.ts`, `cli-worktrain.ts`) so the deps never throw from
112
+ the coordinator's perspective.
113
+
114
+ **Tensions resolved:** DI purity -- the mode files stay clean. Accepts: changes to 2 files
115
+ outside the permitted scope.
116
+
117
+ **Boundary:** At the injection layer. Wrong boundary for this task -- the coordinator owns the
118
+ escalation invariant, not the injectors. Injectors wire up the real implementation; they are
119
+ not responsible for the coordinator's recovery behavior.
120
+
121
+ **Failure mode:** Wrapping at injection site catches throws but cannot return `PipelineOutcome`
122
+ -- would need to return null or a sentinel, which the mode files then check. Adds complexity
123
+ at both ends, solving neither fully.
124
+
125
+ **Repo-pattern relationship:** Departs -- existing injected deps use Result types for
126
+ error-returning deps; plain-promise deps are not wrapped at injection sites.
127
+
128
+ **Scope:** Too broad -- reaches outside the permitted 3 files.
129
+
130
+ **Verdict: Rejected.** Out of scope and wrong seam.
131
+
132
+ ---
133
+
134
+ ### Candidate 3: Private helper `safePostToOutbox(deps, msg, meta)`
135
+
136
+ **Summary:** Extract a private helper that wraps `deps.postToOutbox` in try/catch, reducing
137
+ repetition at the 8+ `postToOutbox` call sites.
138
+
139
+ **Tensions resolved:** DRY for `postToOutbox`. Accepts: new abstraction not prescribed by task.
140
+
141
+ **Boundary:** Same 3 mode files, plus a local helper function in `implement-shared.ts`.
142
+
143
+ **Failure mode:** Helper abstraction obscures the try/catch from reviewers; may hide future
144
+ misuse (e.g., someone using the helper for a call that SHOULD escalate on failure).
145
+
146
+ **Repo-pattern relationship:** No precedent for dep-wrapper helpers in the mode files. `archiveFile`
147
+ try/catch is inline without a helper.
148
+
149
+ **Scope:** Best-fit only if `postToOutbox` had 15+ sites. At 8, YAGNI says no.
150
+
151
+ **Verdict: Skipped.** Task spec gives explicit inline pattern. YAGNI applies.
152
+
153
+ ---
154
+
155
+ ## Comparison and Recommendation
156
+
157
+ **Candidate 1 is the clear choice.**
158
+
159
+ All three candidates converge on the same underlying mechanism (try/catch). The only real
160
+ alternatives differ in location (injection site -- wrong boundary) or DRY abstraction (helper --
161
+ not warranted at 8 sites). Convergence is honest here.
162
+
163
+ Candidate 1:
164
+ - Follows the prescribed pattern from the task description exactly
165
+ - Follows the repo precedent (`archiveFile`, `writeFile` try/catch)
166
+ - Is locally visible and reviewable
167
+ - Carries zero happy-path risk
168
+ - Can be applied systematically to all confirmed call sites
169
+
170
+ ---
171
+
172
+ ## Self-Critique
173
+
174
+ **Strongest argument against:** The 8+ `postToOutbox` call sites produce repetitive code. If
175
+ the count grew to 20+, a helper would be clearly warranted. At 8, the verbosity is manageable.
176
+
177
+ **Narrower option that might work:** Only fix `getAgentResult` and `pollForPR` (HIGH severity),
178
+ skip `postToOutbox` wrapping (MEDIUM severity). Would reduce scope. Loses: `postToOutbox` crash
179
+ at escalation decision points is still a real failure mode that kills the pipeline silently.
180
+
181
+ **Broader option:** Candidate 2 (wrap at injection). Would be justified only if there were a
182
+ precedent of wrapping injected deps at the injection layer. No such precedent exists.
183
+
184
+ **Invalidating assumption:** If `postToOutbox` is guaranteed never to throw in production (e.g.,
185
+ the real impl is in-memory rather than disk-based). The audit doc confirms it uses
186
+ `fs.promises.appendFile` -- can fail on disk full or permission error. Assumption holds.
187
+
188
+ ---
189
+
190
+ ## Open Questions for Main Agent
191
+
192
+ None. The problem, solution, and scope are fully specified. Implementation is mechanical.
193
+
194
+ - Confirm `pollForPR` in `full-pipeline.ts` line 454 also needs wrapping (not explicitly in
195
+ task description but confirmed unsafe -- include it).
196
+ - For `postToOutbox`: the task says "log a warning and continue". Use `process.stderr.write()`
197
+ as prescribed, not `deps.stderr()`.
198
+ - UX gate zombie detection in `implement.ts`: add `if (!uxHandle || uxHandle.trim() === '')` guard
199
+ after line 144, consistent with all 9 other session handle checks in the coordinator.
@@ -0,0 +1,120 @@
1
+ # Coordinator I/O Error Handling -- Design Review Findings
2
+
3
+ Generated: 2026-04-19
4
+
5
+ ## Tradeoff Review
6
+
7
+ ### Verbosity (8+ `postToOutbox` inline try/catch sites)
8
+
9
+ - **Verdict:** Acceptable. At 8 sites, YAGNI wins. The `archiveFile` precedent in the same files
10
+ shows inline try/catch is the established pattern.
11
+ - **Break condition:** If `postToOutbox` call sites grow to 15+, extract a private helper.
12
+ - **Hidden assumption:** `postToOutbox` call count stays roughly constant in the near term.
13
+
14
+ ### `deps.stderr()` vs. `process.stderr.write()` in catch blocks
15
+
16
+ - **Verdict:** Use `deps.stderr()` to match the existing `archiveFile` pattern in `implement.ts`
17
+ and `full-pipeline.ts`. The task spec example uses `process.stderr.write()` but the actual repo
18
+ uses `deps.stderr()` for the identical use case. `deps.stderr()` is more consistent and testable.
19
+ - **Break condition:** None. `deps.stderr()` is strictly better here.
20
+
21
+ ### `pollForPR` in `full-pipeline.ts` not in task description but included
22
+
23
+ - **Verdict:** Include it. It's the same unsafe call pattern, same dep, same risk. Excluding it
24
+ would leave the fix incomplete.
25
+ - **Hidden assumption:** Both `pollForPR` call sites use the same real implementation -- confirmed.
26
+
27
+ ---
28
+
29
+ ## Failure Mode Review
30
+
31
+ | Mode | Handled? | Risk | Notes |
32
+ |------|----------|------|-------|
33
+ | Missing a call site | Mitigated | Medium | Grep check after implementation |
34
+ | `postToOutbox` throw during escalation sequence | Yes | Low | `return` is on next line after try/catch |
35
+ | `getAgentResult` throw with non-Error | Yes | Low | `e instanceof Error ? e.message : String(e)` |
36
+ | `pollForPR` throw leaving `prUrl` uninitialized | Yes (with care) | Medium | Must use `let prUrl; try {...} catch -> return escalated` |
37
+ | UX gate empty `uxHandle` zombie | Yes (after fix) | Low | Same 4-line guard as 9 other handles |
38
+
39
+ **Highest-risk failure mode:** `pollForPR` catch block structure. If written incorrectly (catch
40
+ logs but falls through), `prUrl` would be undefined and the subsequent `if (!prUrl)` check would
41
+ catch it -- but the `prUrl` variable would need to be declared with `let` outside the try block.
42
+ The fix requires care in the variable declaration pattern.
43
+
44
+ ---
45
+
46
+ ## Runner-Up / Simpler Alternative Review
47
+
48
+ **Runner-up:** Private `safePostToOutbox` helper.
49
+ - **Strength worth borrowing:** Standardized log message format across all `postToOutbox` sites.
50
+ - **Adopted:** Standardize the log message format inline (consistent `[WARN coordinator] postToOutbox failed: ...` prefix across all sites).
51
+ - **Rejected:** Full helper extraction. YAGNI at 8 sites. No precedent in repo.
52
+
53
+ **Simpler alternative:** Skip `postToOutbox` wrapping (only fix `getAgentResult` and `pollForPR`).
54
+ - **Rejected:** `postToOutbox` crashes at critical escalation points. Medium severity is still a
55
+ production crash path that must be fixed.
56
+
57
+ ---
58
+
59
+ ## Philosophy Alignment
60
+
61
+ | Principle | Status |
62
+ |-----------|--------|
63
+ | Errors are data | Fully satisfied -- throws become `PipelineOutcome` values |
64
+ | Escalation-first invariant | Enforced -- no throw-exit paths remain after fix |
65
+ | Make illegal states unrepresentable | Satisfied -- coordinator now always returns a value |
66
+ | DI for boundaries | Satisfied -- no new imports, changes are in mode files only |
67
+ | Compose with small functions | Under acceptable tension -- functions grow slightly |
68
+ | Document why not what | Needs 1-line comment per postToOutbox catch explaining non-fatal rationale |
69
+ | YAGNI with discipline | Satisfied -- no speculative helper |
70
+
71
+ ---
72
+
73
+ ## Findings
74
+
75
+ ### Yellow: `pollForPR` variable declaration pattern
76
+
77
+ The `let prUrl` declaration must be placed BEFORE the try/catch block (not inside it) so that
78
+ the catch block can `return` an escalated outcome and the variable remains in scope after. If
79
+ the variable is declared inside `try`, TypeScript will not compile. This is a known TypeScript
80
+ pattern but worth flagging explicitly.
81
+
82
+ **Fix:** Use the explicit two-step pattern from the task spec:
83
+ ```typescript
84
+ let prUrl: string | null;
85
+ try {
86
+ prUrl = await deps.pollForPR(branchPattern, PR_POLL_TIMEOUT_MS);
87
+ } catch (e) {
88
+ const msg = e instanceof Error ? e.message : String(e);
89
+ deps.stderr(`[WARN coordinator] pollForPR threw: ${msg}`);
90
+ return { kind: 'escalated', escalationReason: { phase: 'pr-detection', reason: `pollForPR threw: ${msg}` } };
91
+ }
92
+ if (!prUrl) { ... }
93
+ ```
94
+
95
+ ### Yellow: `deps.stderr()` vs. `process.stderr.write()`
96
+
97
+ Use `deps.stderr()` in catch blocks. The task spec example uses `process.stderr.write()` but the
98
+ repo's `archiveFile` catch blocks use `deps.stderr()`. Consistency with repo pattern wins.
99
+
100
+ ---
101
+
102
+ ## Recommended Revisions
103
+
104
+ 1. Use `deps.stderr()` (not `process.stderr.write()`) in all catch blocks.
105
+ 2. Use `let prUrl: string | null` declared before the try block for `pollForPR` calls.
106
+ 3. Add a one-line comment in each `postToOutbox` catch explaining non-fatal policy:
107
+ `// postToOutbox write failure is non-fatal -- escalation still returns below`
108
+ 4. Include `pollForPR` in `full-pipeline.ts` even though task description only names `implement.ts`.
109
+ 5. Include UX gate zombie detection fix in `implement.ts` line 144.
110
+
111
+ ---
112
+
113
+ ## Residual Concerns
114
+
115
+ - **No tests for throw injection:** This PR fixes the runtime behavior but adds no tests for
116
+ the throw paths. Tests are a planned follow-up (per the audit doc). The absence of tests means
117
+ a regression in this fix would not be caught by CI. Low concern for this PR -- the fix is
118
+ mechanical and the pattern is simple.
119
+ - **`adaptive-pipeline.ts` line 362 `postToOutbox` is unguarded** but is explicitly out of scope
120
+ for this task. Should be addressed in a follow-up.
@@ -7299,3 +7299,55 @@ The daemon heartbeat + DaemonRegistry membership is the key insight: if the sess
7299
7299
  ### Priority
7300
7300
 
7301
7301
  Medium-high. True session status makes WorkTrain trustworthy as an autonomous system -- operators can see exactly what's happening without guessing. Especially important as session durations get longer (55-minute discovery sessions, 120-minute pipeline runs).
7302
+
7303
+ ---
7304
+
7305
+ ## Workflows tab: incorrect source attribution for bundled workflows (Apr 21, 2026)
7306
+
7307
+ **The bug:** The Workflows tab in the console shows bundled workflows (e.g. `coding-task-workflow-agentic`, `workflow-for-workflows`) as coming from "User Library" instead of "WorkRail Built-in". This is a WorkRail MCP server issue, not a WorkTrain issue.
7308
+
7309
+ **Likely cause:** The source attribution logic reads from the workflow's loaded source (the `WorkflowSource` type). When a workflow exists in both the bundled set AND a user's managed sources or remembered roots, the source returned is the one that "wins" in the storage layer -- which may be the user path rather than the bundled path. Or the `source.kind` field is incorrectly set to `'personal'` for workflows that were loaded from the bundled workflows directory.
7310
+
7311
+ **Where to look:**
7312
+ - `src/infrastructure/storage/schema-validating-workflow-storage.ts` -- source kind propagation
7313
+ - `src/mcp/handlers/shared/workflow-source-visibility.ts` -- how source is mapped to display label in `list_workflows`
7314
+ - `src/infrastructure/storage/file-workflow-storage.ts` -- how `source.kind` is assigned when loading from disk
7315
+
7316
+ **Expected behavior:** Workflows in the `workflows/` directory of the workrail package should always display as "WorkRail Built-in" regardless of whether the user also has a managed source that happens to include the same directory.
7317
+
7318
+ **Priority:** Low for WorkTrain (doesn't affect functionality). Medium for WorkRail MCP (misleading UI, users may think they accidentally modified bundled workflows).
7319
+
7320
+ ---
7321
+
7322
+ ## Coordinator-managed git state and agent crash recovery (Apr 21, 2026)
7323
+
7324
+ ### Git state management (coordinator's job)
7325
+
7326
+ Before dispatching any WorkTrain session that does git work:
7327
+ 1. Check for `.git/index.lock` -- if present, verify the owning PID is dead (via `lsof` on macOS), then remove it
7328
+ 2. Abort any in-progress git operations: `git rebase --abort; git merge --abort`
7329
+ 3. Verify the workspace is in a clean state before handing off to the agent
7330
+
7331
+ Every session that touches files gets a worktree (already implemented). The coordinator ensures worktrees are created cleanly and removed after session completion. The orphan TTL cleanup (24h) handles crash cases.
7332
+
7333
+ ### Agent crash recovery (coordinator's job)
7334
+
7335
+ An agent can die from: stream watchdog timeout (600s no progress), OOM kill, or SIGKILL. In all cases the session event log is intact -- the full conversation history is preserved.
7336
+
7337
+ **The coordinator should detect and recover automatically:**
7338
+
7339
+ 1. Monitor child sessions via `worktrain await`
7340
+ 2. If a session returns `_tag: 'aborted'` or `_tag: 'timeout'` mid-pipeline:
7341
+ - Check if the session made meaningful progress (step advances > 0, or notes written)
7342
+ - If yes: resume the session -- same session ID, same context, agent picks up at last checkpoint
7343
+ - If no (zero progress): retry from scratch with a fresh session, same context bundle
7344
+ 3. Retry up to N times (configurable, default 2) before escalating to Human Outbox
7345
+ 4. Track which phase failed and inject a hint on retry: "Previous attempt failed at this step. Retry with fresh approach."
7346
+
7347
+ **This is session continuation applied to crash recovery.** The agent's conversation history is fully preserved. Resuming puts it back exactly where it was. The 600s watchdog timeout (the most common failure) almost always means a hung LLM call or a tool timeout -- resuming naturally retries the step.
7348
+
7349
+ **Implementation:** `runFullPipeline` and `runImplementPipeline` already have per-phase error handling. Extend each `awaitSessions` call: on non-success outcome, attempt resume before returning escalated. The resume logic is `worktrain session continue <sessionId>` (once that command exists) or `dispatchAdaptivePipeline` with the existing session's context.
7350
+
7351
+ ### Priority
7352
+
7353
+ High. Agent crash recovery makes the overnight-autonomous bar achievable. Without it, any hung LLM call or tool timeout fails the entire pipeline silently. With it, transient failures are automatically retried and the pipeline continues.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exaudeus/workrail",
3
- "version": "3.59.3",
3
+ "version": "3.59.4",
4
4
  "description": "Step-by-step workflow enforcement for AI agents via MCP",
5
5
  "license": "MIT",
6
6
  "repository": {