Repository navigation
fix: hooks resolve .gitmem like the server; session_close names the payload path (GIT-99) - #39
Merged
4 commits merged intoSep 22, 2026
Merged
4 commits merged into
4 commits merged into
Conversation
…rint the payload path (GIT-99) Since GIT-91 the server resolves its root independently of cwd, but the hooks still read ".gitmem/..." relative to theirs. A hook running in a repo read a different store from the one the server writes: no session, or someone else's, and a closing-payload.json the server never reads. - hooks/scripts/resolve-root.sh: GITMEM_DIR, then GITMEM_HOME/.gitmem, then ~/.gitmem, made absolute. Sourced by recall-check, post-tool-use and session-close-check. - Registry entries on this host whose pid is dead are skipped; entries without a pid or from another host are kept. recall-check takes the newest live session instead of sessions[0]. - The Stop hook prints the resolved absolute payload path, JSON-escaped. - Fixes a pre-existing break: without jq, session-close-check's node fallback was brace-expanded by bash into two broken node calls, so the Stop hook never blocked (3 of 32 hook tests red on main on a jq-less machine). test-hooks.sh: the existing tests relied on cwd resolution (cd $TMPDIR finding $TMPDIR/.gitmem); they now export GITMEM_DIR to the same store. +12 tests (44/44; main 31/44). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ose with (GIT-99) A standard close with no closing-payload.json and no inline reflection now returns "closing-payload.json not found at <absolute path>" instead of the "requires task_completion / closing_reflection with N answers" family, which sent agents to rewrite answers they had already written to the wrong root. An unparseable payload is reported as found but unreadable, at its path. session-close-supabase.test.ts: three title/decision tests closed "standard" with neither payload nor reflection, reaching persistence only because validation is mocked. That input is now rejected up front, so they carry a minimal reflection to keep testing what they test. +5 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ad path (GIT-99)
Exits 1 on 1.10.0 ("requires closing_reflection with 7 answers"); exits 0
with the fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
resolve-root.sh decided liveness with `ps -p`. On an image without ps (e.g. node:*-slim, the natural jq-less CI runner) that call fails, so every registry entry looked dead and the Stop hook never blocked — the GIT-112 failure again, through a different missing tool. gitmem_pid_alive uses the kill -0 builtin, with EPERM counted as alive. +2 hook tests (46/46): the check with no ps on PATH, and an EPERM pid. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
neoneye
pushed a commit
to agent-memory-atlas-archive/gitmem-dev--gitmem
that referenced
this pull request
Sep 22, 2026
…itmem-dev#37) GIT-86 (gitmem-dev#37) made session_close's registry fallback look the session up by id. Four mocks of active-sessions.js exported only findSessionByHostPid — session-close-validation (named in the stack plan), session-close-payload-path (gitmem-dev#39, whose mock header was copied from it), session-close-supabase and session-close-bridge. No test reached that fallback, so nothing failed; any future one would have thrown on the missing export. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
neoneye
pushed a commit
to agent-memory-atlas-archive/gitmem-dev--gitmem
that referenced
this pull request
Sep 22, 2026
Conflicts: - session-close.ts: gitmem-dev#39's payloadReadError and gitmem-dev#40's sessionRowStored are separate declarations; both kept. - blank-supabase.mjs: gitmem-dev#40's resolve step before gitmem-dev#39's no-payload close (both before the real close); both result fields; all three checks. Semantic (tsc): gitmem-dev#39's "closing-payload.json not found" return predates WriteResult; it is a pre-write exit, so it gets ...notStored().
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.
What
The hooks find the same
.gitmemthe server uses, and a close with nothing to close with says where it looked (GIT-99).hooks/scripts/resolve-root.shresolves$GITMEM_DIR, then$GITMEM_HOME/.gitmem, then~/.gitmem, and makes the result absolute. This mirrorssrc/services/gitmem-dir.ts.recall-check.sh,post-tool-use.shandsession-close-check.shsource it.recall-checknow uses the newest live session instead ofsessions[0]. Liveness is checked with thekill -0builtin (EPERM counts as alive), notps. The first version of this PR usedps -p, and on an image withoutps(e.g.node:*-slim) every session looked dead, so the Stop hook never blocked. That was found while building the jq-less CI job (GIT-83) and fixed inc18e82a.WRITE structured payload to /abs/.gitmem/closing-payload.json (this exact absolute path — it is where session_close reads).session_close: a standard close with no payload file and no inline reflection returnsclosing-payload.json not found at <absolute path>instead of the "requires N answers" family. An unparseable payload is reported as found but unreadable, at its path.Why
Since GIT-91 the server resolves its root independently of cwd, but the hooks still read
.gitmem/...relative to their own cwd. So a hook running in a repo read a different store from the one the server writes. It saw no session (or someone else's), and it told the agent to write a payload the server never reads. The server then answered:That sent the agent back to re-answer questions it had already answered, when the real problem was the path.
jq(fixed here, GIT-112)Changelog (Release C, its own line): "Stop enforcement was inactive on machines without jq; fixed." A CI hook-test run on a runner without
jqcomes with GIT-83.On a machine without
jq,session-close-check.shfalls back to inlinenode -e "...{...,...}..."inside$( … ). Bash brace-expands that into two brokennodeinvocations, so the session count is empty and the Stop hook never blocks. That's why 3 of 32 hook tests are red onmainon this machine:session_start + >5 calls → blocks,session_start + >5 min → blocks, andNo state dir + session → creates state dir.resolve-root.shpasses the path through the environment into single-quoted JS, so the new code isn't affected. All three pass on this branch.How to test
Hooks:
bash hooks/tests/test-hooks.shmainscriptsjq)mainnever blocksThe new tests cover: precedence (
GITMEM_DIR>GITMEM_HOME>HOME, nevercwd/.gitmem); a relativeGITMEM_DIRmade absolute; the Stop hook reading the store from an unrelated cwd while ignoring a decoycwd/.gitmem; the absolute path in the text; valid JSON, including a path with a quote in it; a dead-pid entry skipped; an other-host entry kept; newest-live ordering; andrecall-checkreading the live session's scars.Venue (v1.8.0
setup.sql,vujndhcumgroztfkkdus; production not touched):flownow callssession_close(standard, no payload, no reflection) before the real close.main: exit 1. It returned the three "requires …" errors above.closing-payload.json not found at /var/folders/…/gitmem-venue-git99-flow2-flow-…/.gitmem/closing-payload.json. Write the closing payload to exactly that path (or pass closing_reflection inline), then call session_close again.The real close then persisted, relevance was readable, and thread counts matched.Unit tests: 1279/1279 pass (+5,
session-close-payload-path.test.ts). The two "missing/unreadable" tests fail onmain.Existing tests that encoded the old behaviour (changed, not deleted)
hooks/tests/test-hooks.sh: every registry test planted$TMPDIR/.gitmemand relied oncd $TMPDIR, i.e. cwd-relative resolution, which is exactly the bug. The suite now exportsGITMEM_DIR="$TMPDIR/.gitmem": same store, named explicitly. Left unpointed, it would now read the developer's real~/.gitmem. No assertion changed.session-close-supabase.test.ts:updates generic title…,preserves custom session titleandpersists decision titles…called a standard close with no payload and no reflection. They only reached persistence becausevalidateSessionCloseis mocked tovalid: true; unmocked, that input was already rejected. It's now rejected up front, so these tests pass a minimalclosing_reflectionand keep testing titles and decisions. No assertion changed.Not in this PR (flagged)
session-start.shstill reads.gitmem/config.jsonfrom cwd for its project hint. It isn't one of the three registry readers; it can sourceresolve-root.shthe same way.CLOSING_QUESTIONShas 9.<cwd>/.gitmem/hooks. They still work there:resolve-root.shsits beside them and ignores cwd.Checklist
npm run test:unitpasses (1279)hooks/tests/test-hooks.sh46/46hooks/README.mddoesn't describe root resolutionBreaking changes
Hooks no longer read
<cwd>/.gitmem. A setup that relied on a project-local store must name it withGITMEM_DIR, which is already what the server requires since GIT-91.🤖 Generated with Claude Code