fix: hook-wrapper variable expansion + embed instructions in binary#4
Merged
Merged
Conversation
Two real-installation bugs surfaced while running curl|bash from main.
1. install.sh: heredoc expanded $HARNESS/$1/MEMPALACE_HOOK_HARNESS at
generation time
- The unquoted heredoc terminator `<<EOF` causes the outer
install.sh shell (running with `set -u`) to expand variable refs
in the wrapper body. $HARNESS and $1 are unbound in the
installer's own scope, so they emit:
main: line 544: HARNESS: unbound variable
main: line 558: HARNESS: unbound variable
to stderr (and would have failed under `set -e` if the heredoc
hadn't already started writing).
- Worse: the resulting wrapper had $HARNESS/$1 stripped to empty
strings, which would silently break `mempal_save_hook.sh codex`
and `MEMPALACE_HOOK_HARNESS=codex mempal_save_hook.sh`.
- Fix: backslash-escape the variables we want preserved
verbatim while keeping ${bin_path} substituted at install time.
2. cli.rs: `mpr instructions <topic>` failed on packaged binaries
- The previous implementation computed
`PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../instructions")`
at compile time, baking the build machine's path into the
released binary. A user running `mpr instructions init` after
curl|bash hit:
Error: Instructions file not found:
/home/runner/work/mempalace_rust/mempalace_rust/crates/core/../../instructions/init.md
because that path doesn't exist on their machine.
- Fix: switch to `include_str!` for all five instruction files so
the bodies travel with the binary regardless of install location.
- Removed the now-unused `INSTRUCTIONS_DIR` LazyLock.
- Added regression test
`test_instruction_content_is_embedded_not_runtime_path` that
calls `run_instructions` for every supported topic; the
pre-fix code would have failed it inside any sandbox without
access to the build-time manifest dir.
Tests: 387/387 passing locally (was 386; +1 new regression test).
Verified end-to-end on local machine:
- Re-ran install.sh with `HOME=/tmp/...` — no HARNESS warnings, wrapper
generated correctly with literal $HARNESS, executes with default
claude-code harness, rejects unsupported harness.
- Built local binary, ran `mpr instructions init|search|mine|help|status`
from /tmp — all print embedded content, no "file not found" error.
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.
Summary
Two real-installation bugs surfaced while running
curl | bashfrommain— both pre-existing, both prevent the published binary from working correctly out of the box.Bug 1: install.sh leaked
$HARNESS/$1expansion at generation timeThe two
cat > "$hooks_dir/...sh" <<EOFblocks for the Claude/Codex hook wrappers used unquotedEOF, so the outerinstall.sh(running withset -u) tried to expand$HARNESS,$1, andMEMPALACE_HOOK_HARNESSwhile writing the wrapper. This produced:…on every install. Worse, the resulting wrapper had
$HARNESSand$1stripped to empty strings, so:./mempal_save_hook.sh codex— argument ignored (always ran claude-code).MEMPALACE_HOOK_HARNESS=codex ./mempal_save_hook.sh— env var ignored.case "$HARNESS"collapsed tocase ""and hit the unsupported-harness branch.Fix: backslash-escape the variable refs we want preserved verbatim (
\${1:-...},\${MEMPALACE_HOOK_HARNESS:-...},\$HARNESS) while leaving${bin_path}to be substituted at install time. Added an inline comment explaining the escaping.Bug 2:
mpr instructions <topic>failed on every packaged binaryThe released v0.1.5 binary fails:
…because
cli.rsresolved the instructions directory via:env!("CARGO_MANIFEST_DIR")is a compile-time macro — it bakes the build machine's path into the binary. On the GitHub Actions runner that's/home/runner/work/mempalace_rust/mempalace_rust/crates/core, which obviously doesn't exist on a user's machine. Sompr instructions init|search|mine|help|statuswas 100% broken in every release tarball ever shipped.Fix: switch to
include_str!. The five instruction.mdfiles are embedded into the binary at compile time, so the topic text travels with the binary regardless of where it gets installed. Removed the unusedINSTRUCTIONS_DIRLazyLock.Files changed
install.sh— escaped variable refs in two hook-wrapper heredocs.crates/core/src/cli.rs— replacedINSTRUCTIONS_DIR+ runtime file read with fiveinclude_str!constants, simplifiedrun_instructions()to amatch. Added regression testtest_instruction_content_is_embedded_not_runtime_path.Review & Testing Checklist for Human
\$HARNESS,\$1, and\$MEMPALACE_HOOK_HARNESSare escaped and that${bin_path}remains unescaped.curl -fsSL https://raw.githubusercontent.com/quangdang46/mempalace_rust/main/install.sh | bashon a fresh box and verify (a) noHARNESS: unbound variablewarning appears in the install output, and (b)mpr instructions initprints the topic content instead of a "file not found" error.mempal_save_hook.sh codexto confirm the wrapper now respects the harness argument (previously it was silently ignored).Notes
install.shwithHOME=/tmp/mpr_install_e2e/home— no warnings, wrapper script reads exactly as intended, executes with defaultclaude-code, rejects unsupported harness.mpr instructions init|search|mine|help|statusfrom/tmp(away from source) — all print embedded content.run_instructions(name)succeeds for every supported topic. The pre-fix implementation would have failed this test in any sandbox without access to the build-time manifest dir.instructions/*.mdsource files are unchanged — only the load mechanism switches from runtime read to compile-time embed.