Skip to content

fix(railguard): skip test hook when runner missing; add path-fence guidance rules#35

Merged
ulises-c merged 3 commits into
mainfrom
fix/railguard-issues
Jun 10, 2026
Merged

fix(railguard): skip test hook when runner missing; add path-fence guidance rules#35
ulises-c merged 3 commits into
mainfrom
fix/railguard-issues

Conversation

@ulises-c

@ulises-c ulises-c commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Fixes the two open Railguard issues.

#29 — Railguard Hooks Error (blocking pytest: command not found)

post-test-runner.sh auto-detects pytest for any project with a pyproject.toml/pytest.ini/setup.py, then fails with a blocking PostToolUse error when the runner isn't installed. Added a guard after test-command discovery:

command -v "${TEST_CMD%% *}" >/dev/null 2>&1 || exit 0

A missing runner binary now means "no runnable suite — skip" (consistent with the hook's existing no-suite behavior) instead of a blocking error. This covers all detected runners (pytest, cargo, go, npm, make) and .claude/test-cmd overrides, without requiring a global pytest install.

Closes #29

#26 — path-fence false positives on fenced paths in command text

Added rules/common/railguard.md (imported from CLAUDE.md, outside the railguard-managed block so it survives regeneration) codifying:

  • Prefer Write/Edit tools over cat <<EOF > / echo > / printf > for authoring file content.
  • Don't embed fenced path literals in Bash command text; reference them via files written with Write and passed by path (--body-file, --file).
  • Switching from a fence-blocked Bash command to the Write tool is the intended remediation, not evasion.
  • A block on an actual fenced-path access should not be retried in any form.

Since ~/.claude/CLAUDE.md and ~/.claude/rules/ are symlinked into this repo, the guidance is live immediately.

Closes #26

#26 follow-up — cd approval prompts (fence anchor drift)

Root-caused the constant cd prompts in monorepo/submodule work: the fence's "project directory" is the hook's cwd at each call, not a root captured at session start. Since the shell cwd persists across Bash calls, a cd into a nested dir re-anchors the fence there, and cd back to the repo root resolves "outside the project" → approval prompt. Reproduced live (the repo root itself failed the fence after a nested cd).

Workaround in railguard.yaml: added ~/github, ~/Github, ~/Bitbucket to fence.allowed_paths so cwd drift inside any repo under them stays fenced-in. Details in the #26 comment.

Out of scope / follow-ups

  • Fence matching on actual access targets instead of substring-scanning command text is an upstream railguard binary change, not fixable in this repo.
  • The proper fix for fence anchor drift is also upstream: capture the project root at SessionStart (launch cwd or git rev-parse --show-toplevel) and fence against that stable root instead of the per-call cwd.
  • fix(hooks): post-test-runner.sh broken on macOS — date +%s%3N and missing timeout #33 (post-test-runner.sh macOS portability — date +%s%3N, missing timeout) touches the same script but is a separate fix, left out of this PR.

Security review run on the staged diff: no findings (the hook change adds a read-only, quoted command -v check; the rest is markdown).

🤖 Generated with Claude Code

ulises-c and others added 2 commits June 9, 2026 17:02
…idance rules

- post-test-runner.sh: treat a missing test runner binary (e.g. pytest on a
  fresh box) as "no runnable suite" and exit 0 instead of emitting a blocking
  PostToolUse error (#29)
- add rules/common/railguard.md codifying Write-tool-over-heredoc guidance so
  fenced path strings in file content don't trip the Bash path fence, and so
  switching to Write after a fence block isn't treated as evasion (#26)
- import the new rules file from CLAUDE.md ahead of the railguard-managed block

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fence's "project directory" is the hook cwd at each call, not a root
captured at session start. Persistent shell cwd means a cd into a nested
dir re-anchors the fence there, and cd back to the repo root asks (#26).
Allow ~/github, ~/Github, ~/Bitbucket so cwd drift inside any repo under
them stays fenced-in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ulises-c

Copy link
Copy Markdown
Owner Author

Upstream follow-up filed and submitted for the fence anchor drift root-caused here:

  • Issue: railyard-dev/railguard#4 — Path fence anchors to per-call cwd instead of a session project root — cd within a repo triggers approval prompts
  • PR: railyard-dev/railguard#5 — fix(fence): anchor path fence to session project root, not per-call cwd

The upstream PR captures the project root once at SessionStart (nearest ancestor with .git, falling back to the launch cwd) and fences every subsequent call against that anchor instead of the per-call cwd. It also fixes a second symptom of the same root cause: per-call cwd was used to locate the session state file, so cd silently spawned fresh state files and reset suspicion tracking and session approvals.

Once that lands in a release, the fence.allowed_paths workaround in railguard.yaml (~/github, ~/Github, ~/Bitbucket) can be dropped.

@ulises-c
ulises-c marked this pull request as ready for review June 10, 2026 06:42
@ulises-c
ulises-c merged commit 6c7d564 into main Jun 10, 2026
3 checks passed
@ulises-c
ulises-c deleted the fix/railguard-issues branch June 10, 2026 06:42
ulises-c added a commit that referenced this pull request Jun 10, 2026
…ified stack (#36)

Merge origin/main (PRs #28 powerlevel10k/LACT, #35 railguard fixes,
#38 claude-hud) into the unification branch. The two PRs that touched the
legacy setup stack landed while this branch converted it to shims, so their
features are ported into the unified system as part of the conflict
resolution:

- packages.json gains zsh-theme-powerlevel10k (arch/yay, high) and lact
  (arch/yay, medium) — the entries main added to the now-deleted
  linux-desktop/linux_desktop_packages.json.
- The shared desktop flow (lib/core.sh) deploys ~/.p10k.zsh from
  linux-desktop/p10k.zsh.example via deploy_config — the step main added to
  the legacy linux-desktop/setup.sh, which stays a shim.
- Everything else from main merges clean: zshrc.examples, ghostty font,
  p10k.zsh.example, LACT configs/docs, railguard and claude-hud config.

Gate: per-platform full-flag dry-run diffed against the pre-merge snapshot —
macOS and server unchanged (timestamp only); ubuntu/arch show exactly the
intended additions (p10k deploy, the two arch packages, and the zshrc
update caused by main's new zshrc.example content). dryrun-smoke 4/4;
shellcheck + jq clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant