Skip to content

fix(meridian): rewrite hooks in Node, add cross-OS CI#3

Merged
KodingDev merged 3 commits into
masterfrom
claude/user-prompt-eof-error-iBLs7
May 25, 2026
Merged

fix(meridian): rewrite hooks in Node, add cross-OS CI#3
KodingDev merged 3 commits into
masterfrom
claude/user-prompt-eof-error-iBLs7

Conversation

@KodingDev
Copy link
Copy Markdown
Owner

Summary

The plugin hooks crashed on macOS with user-prompt-submit: line 39: unexpected EOF while looking for matching '. macOS ships bash 3.2, which misparses here-documents nested inside $(...) command substitution; the apostrophes in the orientation/audit text (user's, doesn't) then read as an unterminated single quote. Machines with a newer Homebrew bash first on PATH were unaffected — which is why it reproduced on one laptop but not another.

Rather than patch the bash, this rewrites all three hooks in Node and switches hooks.json to exec form ("command": "node", "args": ["…"]), the cross-platform pattern the hooks docs recommend. Claude Code already requires Node, so the hooks now run identically on macOS, Linux, and Windows with no shell involved.

Hooks (plugins/meridian/hooks/)

  • session-start.mjs, user-prompt-submit.mjs, session-end.mjs + shared lib.mjs replace the bash scripts. JSON.parse/JSON.stringify do all parsing and encoding.
  • This removes a whole class of platform traps: the bash 3.2 heredoc bug, BSD-vs-GNU sed/awk differences (BSD sed treated s/\r//g as "delete every r", silently corrupting injected context on macOS), the set -o pipefail bashism, and the hand-rolled JSON escaper.
  • The two large prompt texts move into hooks/context/*.md and are read at runtime — no shell/JSON escaping, editable as plain Markdown.
  • session_id validation (it feeds rm -rf/mkdir paths) is centralized in lib.mjs and still rejects anything non-UUID-shaped.

CI (.github/workflows/ci.yml, new)

  • Matrix across ubuntu / macos / windows. The original failure was macOS-only, so the OS matrix is the regression guard.
  • Runs the hooks the way exec form does (node <hook> + JSON on stdin) via a zero-dependency node:test harness, and runs claude plugin validate --strict on the marketplace + both plugin manifests.
  • --strict validation immediately caught real drift: marketplace.json still pinned meridian at 0.10.3 while plugin.json had moved to 0.10.4. Synced to 0.10.4.

Version

  • meridian plugin 0.10.30.10.4 (so the marketplace surfaces the fix).

Test plan

  • CI green on all three OS (ubuntu/macos/windows)
  • claude plugin validate --strict . passes locally
  • On a macOS machine with only system bash 3.2, install the plugin and confirm a session starts and prompts submit without the EOF error
  • Confirm SessionStart still injects orientation as a system reminder and the 8th prompt still emits the routing audit
  • Confirm em-dashes and apostrophes render correctly in the injected context (the BSD-sed / bash 3.2 regressions)

Generated by Claude Code

claude added 2 commits May 25, 2026 00:39
…bump to 0.10.4

The bash hooks crashed on macOS with "unexpected EOF while looking for
matching '". macOS ships bash 3.2, which misparses here-documents nested
in $(...) command substitution; the apostrophes in the orientation/audit
text ("user's", "doesn't") then read as an unterminated single quote.
Machines with a newer Homebrew bash first on PATH were unaffected, which
is why it reproduced on one laptop but not another.

Rewrite all three hooks (SessionStart, UserPromptSubmit, SessionEnd) in
Node and switch hooks.json to exec form (command: node, args: [path]).
Claude Code already requires Node, so the hooks now run identically on
macOS, Linux, and Windows with no shell involved -- eliminating the
bash-version trap, the BSD-vs-GNU sed/awk differences (BSD sed treated
s/\r//g as "delete every r", silently corrupting injected context), the
bashism set -o pipefail, and the hand-rolled JSON escaping. JSON.parse
and JSON.stringify now do all parsing and encoding.

The two large prompt texts move into hooks/context/*.md and are read at
runtime, so they need no shell/JSON escaping and are editable as plain
Markdown. session_id validation (it feeds rm -rf / mkdir paths) is
centralized in lib.mjs and still rejects anything non-UUID-shaped.
Add a GitHub Actions matrix (ubuntu, macos, windows) that runs the hooks
the way Claude Code's exec form does -- node <hook> with the event JSON
on stdin -- via a zero-dependency node:test harness, and runs
`claude plugin validate --strict` on the marketplace and both plugin
manifests. This is the regression guard for the bash 3.2 crash: the
original failure was macOS-only, so the OS matrix is the point.

The harness asserts the behaviors that were fragile before the Node
rewrite: orientation/audit emit as valid JSON, the em-dash and the
"user's" apostrophe survive the round trip, the every-8th-prompt audit
cadence, 7-day prune, session cleanup, and rejection of unsafe
session_id values before they reach the filesystem.

--strict validation immediately caught real drift: marketplace.json
still pinned meridian at 0.10.3 while plugin.json had moved to 0.10.4.
Sync the marketplace entry to 0.10.4.
@KodingDev KodingDev changed the title Fix macOS hook crash: rewrite hooks in Node + add cross-OS CI fix(meridian): rewrite hooks in Node, add cross-OS CI May 25, 2026
- Add an emitContextFile() helper to lib.mjs and route both hooks through
  it, removing the duplicated "resolve context/, read, trimEnd, emit" block
  and the node:url imports it required.
- Hoist stateRoot() out of session-start's prune loop so it isn't
  recomputed per directory entry.
- Lock the single-line-JSON invariant the rewrite exists to protect: a test
  now fails if the payload ever becomes multi-line (e.g. pretty-printed),
  which JSON.parse alone would not catch.
- Reword the safeSessionId comment (it validates a single path segment, not
  a UUID) and give the remaining empty catches a one-line rationale to match
  the file's WHY-only convention.
- Split CI: manifest validation runs once on ubuntu (it's OS-independent),
  while the OS matrix runs only the Node hook tests -- the real regression
  guard -- without a per-runner CLI install.
@KodingDev KodingDev merged commit f87998b into master May 25, 2026
4 checks passed
@KodingDev KodingDev deleted the claude/user-prompt-eof-error-iBLs7 branch May 25, 2026 01:14
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