Skip to content

fix: hooks resolve .gitmem like the server; session_close names the payload path (GIT-99) - #39

Merged
4 commits merged into
mainfrom
bugfix/GIT-99-hook-root-resolution
Sep 22, 2026
Merged

4 commits merged into
mainfrom
bugfix/GIT-99-hook-root-resolution

Conversation

@nTEG-dev

@nTEG-dev nTEG-dev commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

What

The hooks find the same .gitmem the server uses, and a close with nothing to close with says where it looked (GIT-99).

  • hooks/scripts/resolve-root.sh resolves $GITMEM_DIR, then $GITMEM_HOME/.gitmem, then ~/.gitmem, and makes the result absolute. This mirrors src/services/gitmem-dir.ts. recall-check.sh, post-tool-use.sh and session-close-check.sh source it.
  • Dead pids: registry entries on this host whose pid is dead are skipped. Entries without a pid, or from another host, are kept, since their pid can't be checked here. recall-check now uses the newest live session instead of sessions[0]. Liveness is checked with the kill -0 builtin (EPERM counts as alive), not ps. The first version of this PR used ps -p, and on an image without ps (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 in c18e82a.
  • The Stop hook prints the resolved absolute payload path, JSON-escaped: 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 returns closing-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:

Standard close requires task_completion object. …
Standard close requires closing_reflection with 7 answers
Standard close requires human_corrections field (even if empty string)

That sent the agent back to re-answer questions it had already answered, when the real problem was the path.

⚠️ Found along the way: Stop enforcement was off without 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 jq comes with GIT-83.

On a machine without jq, session-close-check.sh falls back to inline node -e "...{...,...}..." inside $( … ). Bash brace-expands that into two broken node invocations, so the session count is empty and the Stop hook never blocks. That's why 3 of 32 hook tests are red on main on this machine: session_start + >5 calls → blocks, session_start + >5 min → blocks, and No state dir + session → creates state dir. resolve-root.sh passes 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.sh

main scripts This branch
Existing 32 29 pass: the 3 above fail (no jq) 32 pass
New 12 (GIT-99) 2 pass: vacuous, since main never blocks 12 pass
Total 31/44 46/46 (44 + 2 for the pid check)

The new tests cover: precedence (GITMEM_DIR > GITMEM_HOME > HOME, never cwd/.gitmem); a relative GITMEM_DIR made absolute; the Stop hook reading the store from an unrelated cwd while ignoring a decoy cwd/.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; and recall-check reading the live session's scars.

Venue (v1.8.0 setup.sql, vujndhcumgroztfkkdus; production not touched): flow now calls session_close (standard, no payload, no reflection) before the real close.

  • main: exit 1. It returned the three "requires …" errors above.
  • This branch: exit 0. It returned 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 on main.

Existing tests that encoded the old behaviour (changed, not deleted)

  1. hooks/tests/test-hooks.sh: every registry test planted $TMPDIR/.gitmem and relied on cd $TMPDIR, i.e. cwd-relative resolution, which is exactly the bug. The suite now exports GITMEM_DIR="$TMPDIR/.gitmem": same store, named explicitly. Left unpointed, it would now read the developer's real ~/.gitmem. No assertion changed.
  2. session-close-supabase.test.ts: updates generic title…, preserves custom session title and persists decision titles… called a standard close with no payload and no reflection. They only reached persistence because validateSessionClose is mocked to valid: true; unmocked, that input was already rejected. It's now rejected up front, so these tests pass a minimal closing_reflection and keep testing titles and decisions. No assertion changed.

Not in this PR (flagged)

  • session-start.sh still reads .gitmem/config.json from cwd for its project hint. It isn't one of the three registry readers; it can source resolve-root.sh the same way.
  • The Stop hook text still says "7 reflection questions"; the server's CLOSING_QUESTIONS has 9.
  • The installer copies the scripts into <cwd>/.gitmem/hooks. They still work there: resolve-root.sh sits beside them and ignores cwd.

Checklist

  • npm run test:unit passes (1279)
  • hooks/tests/test-hooks.sh 46/46
  • New tests added
  • No schema change; works on a v1.8.0 store
  • Docs: hooks/README.md doesn't describe root resolution

Breaking changes

Hooks no longer read <cwd>/.gitmem. A setup that relied on a project-local store must name it with GITMEM_DIR, which is already what the server requires since GIT-91.

🤖 Generated with Claude Code

Claude and others added 4 commits September 21, 2026 20:20
…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>
@nTEG-dev nTEG-dev closed this pull request by merging all changes into main in f93c3a3 Sep 22, 2026
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().
@nTEG-dev
nTEG-dev deleted the bugfix/GIT-99-hook-root-resolution branch September 23, 2026 05:49
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.

1 participant