feat: hook crash presentation in the Summary (#105)#134
Merged
Conversation
) When a hook crashes (non-fatal, Helm semantics) or emits more output than the Summary shows inline, the orchestrator now writes the complete captured stdout/stderr to a vault-local log so the Summary can truncate on screen and point at the full file. - vault-paths: HOOK_LOGS_DIR + hookLogRelPath(slot) (posix, doubles as the user-facing pointer). Under .shardmind/, so excluded from Invariant 1. - hook.ts: HookSummary.logPath; HOOK_OUTPUT_VISIBLE_LINES + pure headLines() helper shared by the write-decision and the on-screen truncation. - hook-orchestrator: attachHookLog() writes the log only when the hook crashed or its output would truncate; non-fatal (a write failure just omits the pointer); dry-run / skipped slots write nothing. Tests: headLines unit matrix; orchestrator log persistence (crash writes log, long-clean writes log, short-clean writes nothing, dry-run writes nothing, unwritable logs/ path is non-fatal). Part of #105. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…og (#105) A crashed hook is non-fatal, but the Summary dumped its raw multi-line stderr verbatim, so a successful install with a crashed hook looked like an engine failure. Now: - crash line reads "<hook> exited with code N. Non-fatal; your vault is ready." with the "operation succeeded" detail on its own dim line, so the headline stays the command outcome. - stdout/stderr render dimmed + indented, truncated to HOOK_OUTPUT_VISIBLE_LINES via the shared headLines() helper, with a dim "… N more lines — full log at <path>" pointer when there's more (path present when the orchestrator wrote a log). Bold "Hook stdout:" / "Hook stderr:" labels and the contiguous "exited with code N" phrasing are preserved, so existing Summary / UpdateSummary tests stay green. New tests cover crash truncation + log pointer, clean-exit truncation, within-cap (no pointer), and truncation with no log path. Part of #105. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…og (#105) Under a real PTY, a thrown bootstrap hook now renders the reworded crash headline ("exited with code N. Non-fatal; your vault is ready.") with the stderr stack truncated and a "(full log at .shardmind/logs/bootstrap.log)" pointer, instead of dumping the whole stack. The full thrown marker is verified in the on-disk log rather than on screen — robust against any node/tsx stderr preamble shifting it off the 5-line head. Also drops the em-dash from the pointer separator (parenthetical form). Verified no regression: the obsidian-mind contract four-branch hook tests and the Invariant 1 byte-equivalence suite stay green (.shardmind/logs/ is excluded from byte-equivalence). Part of #105. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- CHANGELOG: Changed entry for the truncate/dim/non-fatal Summary + the vault-local full-output log. - SHARD-LAYOUT: add logs/ to the installed-side .shardmind/ tree; note HOOK_LOGS_DIR + that everything under .shardmind/ is excluded from Invariant 1. - ARCHITECTURE §9.3: reword the "if a hook throws" warning to match the new copy and describe truncation + the on-disk log. - AUTHORING §6: tell shard authors where a crashed hook's full output lands. Part of #105. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#105 (hook stderr presentation) was the last open Flagship-UX item, so that gate line is now complete. Also mark the genuinely-finished Done-gate lines: Foundation (#111/#112) and Parallel (#113/#119) both shipped. Hook lifecycle stays unchecked (engine done, but obsidian-mind#75 migration pending); research-wiki (#15), docs polish (#85), and the two-shard smoke remain open. Part of #105. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adversarial audit found one real cross-platform bug and gaps: - fix: headLines split on /\r?\n/ (was '\n'), so a Windows hook's CRLF output no longer leaves a trailing \r that renders as a visible control char; the head re-joins with LF (canonical display). The full on-disk log keeps the hook's raw bytes. - new tests/unit/vault-paths.test.ts: hookLogRelPath is always forward-slash ".shardmind/logs/<slot>.log" (never a backslash, even on win32) and joins to a valid OS-native path under the vault — pins the display contract on the full ubuntu/windows/macos CI matrix. - headLines CRLF unit case; log-format markers pinned in the orchestrator crash test; component edge cases (logPath set but output short → no pointer; crash with empty output → header only). Part of #105. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR improves how non-fatal hook crashes (Helm semantics) are presented in the final Summary so successful installs/updates/adopts aren’t visually buried by multi-line hook stderr, while still preserving full output for debugging.
Changes:
- Add a shared
headLineshelper +HOOK_OUTPUT_VISIBLE_LINES(5) to truncate hook stdout/stderr consistently across the orchestrator and UI, including CRLF normalization. - Persist full hook output to vault-local
.shardmind/logs/<slot>.log(non-fatal best-effort) when a hook crashes or output would truncate; threadlogPaththrough the hook summary. - Update TUI rendering to show a clearer non-fatal crash header, dim/indent truncated output, and include a “full log at …” pointer; add comprehensive unit/component/e2e coverage and docs/roadmap/changelog updates.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/vault-paths.test.ts | Pins forward-slash display-path contract for hook log pointers across OSes. |
| tests/unit/hook.test.ts | Adds unit tests for headLines, including CRLF and trailing-newline behavior. |
| tests/unit/hook-orchestrator.test.ts | Covers log persistence decision + non-fatal write failure behaviors. |
| tests/e2e/tui/hook-lifecycle.test.ts | Validates truncated UI + presence of persisted full stderr marker in log. |
| tests/component/HookSummarySection.test.tsx | Verifies crash header, truncation, pointer rules, and edge cases. |
| source/runtime/vault-paths.ts | Introduces HOOK_LOGS_DIR and hookLogRelPath (forward-slash display path). |
| source/core/hook.ts | Adds HOOK_OUTPUT_VISIBLE_LINES, headLines, and plumbs logPath into HookSummary. |
| source/core/hook-orchestrator.ts | Adds attachHookLog and on-demand .shardmind/logs/<slot>.log writes. |
| source/components/HookSummarySection.tsx | Updates hook Summary UX: non-fatal crash headline, dim/indented truncated output, log pointer. |
| ROADMAP.md | Marks #105 as completed and updates done-gate checkboxes. |
| docs/SHARD-LAYOUT.md | Documents .shardmind/logs/ as engine metadata for persisted hook output. |
| docs/AUTHORING.md | Documents new crash presentation + where to find full hook logs. |
| docs/ARCHITECTURE.md | Updates hook-crash contract section to include truncation + log persistence behavior. |
| CHANGELOG.md | Records user-visible Summary behavior change and log persistence feature. |
…105) CI (ubuntu/macos) caught a wrong assumption in my own test, not a feature bug. The thrown-hook fixture's stack is only ~3 lines — under HOOK_OUTPUT_VISIBLE_LINES (5) — so the Summary does NOT truncate it and shows no "(full log at …)" pointer. The waitForScreen predicate required that pointer and timed out. Fix: assert what actually renders (confirmed by the CI screen dump) — the reworded "exited with code N. Non-fatal; your vault is ready." headline + the marker on screen — and still verify the full output is persisted to .shardmind/logs/bootstrap.log on disk (a crashed hook always writes its log, regardless of truncation). The truncation + pointer path stays covered at the component layer. Part of #105. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #105. Last open Flagship-UX item.
Problem
Hooks are non-fatal (Helm semantics — the vault is already written when a hook runs, so a failing hook never rolls back). But the Summary dumped the hook's raw multi-line stderr verbatim, so a successful install with a crashed hook looked indistinguishable from an engine failure — a Node stack trace dwarfed the "What happened" / "Next" sections.
Change
"<hook> exited with code N. Non-fatal; your vault is ready."with the "operation succeeded" detail on its own dim line — the command outcome stays the headline.HOOK_OUTPUT_VISIBLE_LINES(5) lines, with a"… N more lines (full log at .shardmind/logs/<slot>.log)"pointer..shardmind/logs/<slot>.log(newHOOK_LOGS_DIR) when a hook crashes or its output would truncate. The write is non-fatal (a failure just omits the pointer); short clean hooks write nothing; the log is under.shardmind/, so it's excluded from Invariant 1.Layering: the orchestrator owns the write (
attachHookLog— it hasvaultRoot+ theHookResult+ fs access) and threadslogPaththroughHookSummary; the component only reads it. The 5-line threshold lives once incore/hook.ts(headLines), shared by the write-decision and the render.Cross-platform
CI runs
npm teston ubuntu + windows + macos × node 22/24. The two platform-variable axes are handled by construction and pinned by tests that run on every cell:headLinessplits on/\r?\n/and re-joins with\n(pure string logic) — a Windows hook's CRLF no longer leaves a visible\r. The on-disk log keeps raw bytes.logPathcomes from a forward-slash template (hookLogRelPath), neverpath.join, so all OSes show.shardmind/logs/<slot>.log.path.joinis used only for thefswrite.tests/unit/vault-paths.test.tsasserts the forward-slash contract (no\, even on win32) and the orchestrator test reads the file back via OS-nativepath.join— both run on the Linux + macOS cells. The L2 PTY scenario (Windows-skipped, E2E: bridge SIGINT delivery reliably on GH Actions Windows runner #57) covers the real-terminal render on macOS + Linux.Quality gate
npm run typecheckcleannpm testgreen — 1146 passed / 30 skipped (1125 → 1146, +21 tests)npm run buildcleanany/@ts-ignore/as unknown asin the diff (grep clean).shardmind/logs/excluded from byte-equivalence)Harden audit
Reference bar: #102 (hook lifecycle), #120 (adopt merge) — adversarial enumeration + property/edge tests + cross-OS CI.
Rounds
/simplify(4 agents): clean —headLines/hookLogRelPath/outputBlockhave no existing equivalent;attachHookLogmatches the established non-fatal-write pattern (update-check.ts). Marginal suggestions skipped./harden(adversarial + correctness + docs): 1 real bug + 3 missing tests. Docs + correctness clean.HookSummarybranches intact).Real bug found + fixed
headLines—split('\n')left a trailing\ron Windows hook output that rendered as a visible control char (source/core/hook.ts). Fixed →/\r?\n/.Tests added (unit / component / e2e):
headLinesmatrix incl. CRLF; orchestrator log-persistence (crash / long-clean / short-clean / dry-run / unwritable-non-fatal) + format markers;HookSummarySectiontruncation + pointer + crash-header + 2 edge cases;vault-pathsplatform-invariance; L2 PTY scenario 27 (truncated UI + on-disk marker). 1125 → 1146.Deferrals: none.
Commits
feat(core)— log persistence +headLines+ orchestrator wiringfeat(components)— crash header + truncate/dim + pointertest(e2e)— L2 scenario 27docs— CHANGELOG + SHARD-LAYOUT + ARCHITECTURE §9.3 + AUTHORING §6docs(roadmap)— check Summary: hook stderr presentation buries success when hook crashes #105 + done-gate boxestest(harden)— CRLF fix + cross-platform path guards