Skip to content

Normalize HOME in code-mode paths, so the guard sees a new hint folder - #74

Merged
gabe4coding merged 2 commits into
mainfrom
claude/zealous-kepler-3c6b6c
Oct 26, 2026
Merged

gabe4coding merged 2 commits into
mainfrom
claude/zealous-kepler-3c6b6c

Conversation

@gabe4coding

@gabe4coding gabe4coding commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Intent

Close a gap in the hint-file guard: when $HOME is not normalized (/home/u/, /, /home//u), the first Write into a hint or function folder that does not exist yet must be denied.

Drift

  • Added: the project root is normalized too ($.session.root() goes through the same helper).
  • Added: resolvePath now calls the new normalizePath in hooks/paths.ts. The loop is unchanged, only moved.
  • Not done: no migration of the approval ledger (see Decisions).

Decisions

  • Normalize where the paths are made (codeModePath, guardedDirs), not in homeOf or in isUnder. Then the loaders, the ledger and the guard all use the same path text. A fix in homeOf misses the project root. A fix in isUnder corrects only the compare.
  • The approval ledger key is the hash of the path. On 2026-10-26, the user accepted the one-time re-approval for a HOME that is not normalized: such a person must approve their user hints and saved functions again, one time. This fails closed: the band lists the files. The old // ledger entries are not pruned and stay in $.store.
  • Door: two-way. A revert gives back the old paths and the old ledger keys.

Evidence

Before:

  • Fuzz of resolvePath/isUnder/hintsFolder (separate session, not in this PR) found this input: {"p":"~/.claude/code-mode/hints/x.md","home":"/home/u/","cwd":"/repo/sub","resolved":"/home/u/.claude/code-mode/hints/x.md","guarded":"/home/u//.claude/code-mode/hints"}.
  • z3 counterexample for the isUnder precondition: path="/" dir="//".
  • claude plugin test . with the new tests and the old hooks: 243 pass, 5 fail. The 5 new tests fail, for example (fail) the hint-file guard > guards the folders when HOME is /home/u/ with Received: "written".

After:

  • The same 1,000,000-input fuzz against this branch: pass P4 guard sees a write into HOME hint folder.
  • claude plugin test .: 248 pass, 0 fail.
  • Real Claude Code with this plugin (.claude/skills/verify/drive.mjs), HOME=/Users/gpavanello/:
    • The Write to /Users/gpavanello/.claude/code-mode/hints/verify-guard-probe.md returned code-mode: hint files and saved functions steer the model, so they cannot be written with file tools. ...
    • The Write to note.txt returned File created successfully at: note.txt.
    • No probe file in the hint folder.
    • Limit: this run cannot show the before state. The real hints/ and functions/ folders exist, so their real paths already guarded them. A temporary HOME loses the login.
  • node tests/smoke.e2e.mjs: 11 of 11 pass.
  • Other checks pass: tests/code.check.mjs, tests/docs.check.mjs, tests/runner.integration.mjs, tsc -p tsconfig.pure.json, tsc -p tsconfig.json, scripts/format.sh --check .

Reading map

Tests changed: tests/hints.test.ts

  • 1538-1540: the session helper takes home and root. The defaults keep the old setup.
  • 269-273, 1580-1604: new tests. No assertion removed.

Config: .claude-plugin/plugin.json: version 0.12.16 → 0.12.17.

Public API (exported functions):

  • hooks/paths.ts 10-26: new normalizePath. codeModePath now normalizes.
  • hooks/paths.ts 43-51: guardedDirs normalizes pluginRoot and the plugins folder.
  • hooks/hints.ts 16, 495-498: resolvePath calls normalizePath.

🤖 Generated with Claude Code

guardedDirs built its folders from HOME as the environment gives it. With
HOME=/home/u/ the hint folder was /home/u//.claude/code-mode/hints, and
isUnder needs a normalized folder. The guard also checks the real path of
each folder, but a folder that does not exist yet has none. So the first
Write into a new hint or function folder got past the guard. A file written
this way still did not load without an approval in the ledger.

Move the loop that normalizes a path from resolvePath to normalizePath in
paths.ts. codeModePath and guardedDirs use it, so the loaders and the guard
name the same normalized folders for HOME and the project root. Raise the
version to 0.12.17.

A person whose HOME is not normalized must approve their user hints and
saved functions again one time: the ledger key is the hash of the path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable issues were found.

Summary

Normalizes paths made from HOME and the project root so the loaders and file-tool guard name the same folders.

  • Hint-file writes stay blocked when HOME or the project root has extra slashes.

gabe4coding explicitly accepts one-time reapproval for affected paths without migrating old approval keys.

Reviews (1) · Last reviewed commit: "Normalize HOME in code-mode paths, so th..." · Reviewed by Greptile

resolvePath built the full path in a nested ternary inside the call to
normalizePath. Put the full path in a constant again, as before the
change to normalizePath, and give it to normalizePath. The behavior does
not change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gabe4coding
gabe4coding merged commit 548fddb into main Oct 26, 2026
16 of 17 checks passed
@gabe4coding
gabe4coding deleted the claude/zealous-kepler-3c6b6c branch October 26, 2026 17:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants