Skip to content

feat(hook-manager): add a lefthook adapter - #1895

Merged
potiuk merged 6 commits into
apache:mainfrom
Anurag-S1ngh:feat/1822-hook-manager-lefthook-adapter
Oct 31, 2026
Merged

potiuk merged 6 commits into
apache:mainfrom
Anurag-S1ngh:feat/1822-hook-manager-lefthook-adapter

Conversation

@Anurag-S1ngh

Copy link
Copy Markdown
Contributor

Summary

  • Add the lefthook adapter to the hook-manager registry: no home, the 20 root config names (lefthook.*, .lefthook.*, -local variants), and the call_lefthook hook marker.
  • Implement the hide-global-config install strategy in the shim and generator, so lefthook install through ~/.claude/bin/lefthook writes to $(git rev-parse --git-common-dir)/hooks under a global core.hooksPath, linked worktrees included. The open pre-commit adapter issue hook-manager: add a pre-commit adapter #1820 reuses the same strategy.
  • Add the lefthook-hooks-path agent guard blocking --force and --reset-hooks-path while the global path is the frameworks, and document the npm postinstall gap the shim cannot reach.

Type of change

  • Skill change
  • Tool / bridge contract (tools/<system>/*.md)
  • Python package (tools/*/ with pyproject.toml)
  • Groovy reference impl
  • Cross-cutting (RFC, AGENTS.md, sandbox, privacy-LLM)
  • Documentation (docs/, README.md, CONTRIBUTING.md)
  • Project template (projects/_template/)
  • CI / dev loop (prek, workflows, validators)
  • Other:

Test plan

  • prek run --all-files --skip workspace-pytest passes
  • Python packages touched pass pytest, ruff check, ruff format --check, and mypy: tools/agent-guard, tools/agent-isolation, tools/dev, plugins, tools/sandbox-lint, tools/setup-preflight
  • New tests: shim hide-global-config cases (plain install, --force, -r, linked worktree, help, passthrough), guard deny/allow/override cases, and registry/strategy validation
  • Manual end-to-end with real lefthook v2.2.2 through the shim under a temporary global core.hooksPath: plain and --force land in <repo>/.git/hooks/pre-commit, the shared dir stays empty, a linked worktree lands in the main checkouts .git/hooks/, and the unshimmed binary refuses as expected
  • Guard checked live through agent_guard --check: exit 2 on lefthook install --force, exit 0 on plain lefthook install
  • Skill eval suite: not run. The skill-file edit is the Step P prose describing the shim; no step logic or fixture changed, and render-preflight-fixtures / unittest (skill sibling scripts) pass

Linked issues

Closes #1822
Refs #1820

Notes for reviewers

The shim applies GIT_CONFIG_GLOBAL=/dev/null to every lefthook install, including one carrying --force or --reset-hooks-path, so neither can reach the shared directory or unset the whole-user setup through the shim. MAGPIE_ALLOW_LEFTHOOK_HOOKS_PATH=1 is the deliberate override, and the agent guard blocks an agent from trying.

The npm postinstall path (node_modules/.bin/lefthook, ahead of ~/.claude/bin) is documented but not fixed here; the contract proposes the session-start hook noticing hooks in the shared directory that are not the dispatcher.

@github-actions github-actions Bot added family:setup setup-* skills family:tools tools/* family:docs Docs, MISSION.md, READMEs family:ci .github workflows, prek, validators capability:platform Framework / agent substrate skills (install, verify, doctor, override, status, setup bootstrap) substrate:sandbox Tool substrate: agent isolation, egress control, settings audit substrate:action-guard Tool substrate: deterministic pre-tool-use command guards substrate:framework-dev Tool substrate: build / validate / eval the framework itself contract:hook-manager Tool capability: git-hook managers under the secure setup (install target, sandbox / host homes) labels Oct 29, 2026

@Kaap10 Kaap10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation covers all four requirements of Issue #1822 cleanly. The hide-global-config strategy effectively prevents lefthook from refusing installation when a global core.hooksPath is set, by isolating the git environment via GIT_CONFIG_GLOBAL=/dev/null. The registry additions cover all 20 configuration file variants, the agent guard correctly intercepts destructive flags, and the documentation details the npm package manager postinstall caveat.

Inline comments have been added directly on the respective diff lines for cross-platform portability considerations.

One point with no line in this diff:
PR #1892 (Issue #1891) introduced the Step P.3b eval harness for hook manager shims (step-hook-manager-shim in tools/skill-evals/). While this PR updates the prose documentation in step-p-sandbox-allowlists.md to reference the ~/.claude/bin/lefthook shim path, there is no corresponding eval case added to tools/skill-evals/cases/ yet. Once both PRs land, a follow-up test case should be added to that eval suite.

@Anurag-S1ngh

Copy link
Copy Markdown
Contributor Author

Agreed on the eval follow-up. Leaving the step-hook-manager-shim case for after #1892 lands.

@potiuk potiuk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lefthook adapter and its guard are well tested end to end, and Kaap10's three earlier points are addressed in eff4fb9; the shim should hide the global config only when a global core.hooksPath is actually set, the guard should catch the --flag=true spellings, and the branch needs a rebase onto main.

The branch currently conflicts with main and needs a rebase before this can merge.

Smaller observations

  • See inline comments on tools/agent-isolation/hook-manager-shim.sh:148, plugins/magpie-setup/skills/isolated-setup-install/guards/lefthook_hooks_path.py:44, docs/setup/sandbox-troubleshooting.md:1248.

This review was drafted by an AI-assisted tool and
confirmed by an Apache Magpie maintainer. The findings
below are observations, not blockers; an Apache Magpie
maintainer — a real person — will take the next look at the
PR. If you think a finding is mis-applied, please reply on
the PR and a maintainer will weigh in.

More on how Apache Magpie handles maintainer review:
Contributing guide.

Comment thread docs/setup/sandbox-troubleshooting.md Resolved
Why: lefthook refuses to install while the whole-user scope's global
core.hooksPath is set. The shim hides that value from lefthook's scoped
git config read, so lefthook's own target resolves to the repository's
common hooks directory, linked worktrees included. The registry carries
the adapter with no home because lefthook keeps nothing outside the
repository, and the new lefthook-hooks-path agent guard blocks the two
flags that would damage the setup: --force writes over the dispatcher
and --reset-hooks-path unsets it. The contract records the npm
postinstall path the shim cannot reach.

Generated-by: DeepSeek V4.1 Flash
Why: review flagged three portability and consistency gaps in the lefthook
adapter. guarded_head now recognizes backslash-delimited and .exe command
paths, the hooks-path guard compares framework paths with os.path.normcase,
and the shim's help passthrough uses the same case pattern as the other
install strategy.
Why: the lefthook shim override ran on every host, so an install on a
machine with no global core.hooksPath silently lost the operator's
credential.helper, url.*.insteadOf and safe.directory even though it had
nothing to hide; it now sets GIT_CONFIG_GLOBAL only when the global config
actually carries a core.hooksPath, which makes the documented no-op true.
The guard now reads the flag name before any =value, so lefthook's
--force=true and --reset-hooks-path=true spellings are caught when the
real binary runs outside the shim, while an explicit =false stays allowed.
The new troubleshooting section and the secure-setup paragraph break one
sentence per line.

Also regenerates the isolated-setup fingerprint for the rebase onto main.

Generated-by: DeepSeek V4.1 Flash
@Anurag-S1ngh
Anurag-S1ngh force-pushed the feat/1822-hook-manager-lefthook-adapter branch from eff4fb9 to 440da68 Compare October 30, 2026 06:45
…=false

The lefthook guard read a false-ish `=value` before it looked at short
switch bundles, so `lefthook install -fr=false`, which still sets --force,
was allowed (the previous version denied it). The value now applies only
to a single flag or to the last switch of a bundle; every switch before
it counts as set. Tests deny `-fr=false` and `-rf=false` and keep
`-f=false` allowed.

Also reword the shim comment that called the hand-written install
strategy branches "generated": the sync script generates only the
adapter() block. The secure-setup fingerprint is restamped.

Generated-by: Claude Opus 5
# Conflicts:
#	plugins/magpie-setup/skills/setup/setup_preflight/isolated_fingerprint.py

@potiuk potiuk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

440da68 addresses all three points from the earlier review (the global config is hidden only when a global core.hooksPath is set, the --flag=value spellings, semantic line breaks) and Kaap10's three, each with a test that fails on the previous commit. Two small things were left, so I pushed a fixup commit (772474e) rather than send it back:

  • guards/lefthook_hooks_path.py: the false-ish =value was read before the short-switch bundle check, so lefthook install -fr=false, which still sets --force, was allowed. The value now applies only to a single flag or the last switch of a bundle. New deny cases -fr=false and -rf=false, and -f=false stays allowed (tools/agent-guard/tests/test_skill_guards.py).
  • tools/agent-isolation/hook-manager-shim.sh: the comment no longer calls the install-strategy branches "generated"; sync-hook-managers.py generates only the adapter() block. The secure-setup fingerprint is restamped.
  • 962092a merges main, which had moved the fingerprint constant; it is regenerated for the merged tree.

For a follow-up rather than this PR: the guard reads the first non-option token as the subcommand, so a global option given its value as a separate argument before install (lefthook --colors off install --force) is not recognised, and package runners (npx lefthook install --force) are not unwrapped; and the basename match lets /usr/bin/git reach the bundled guards while git:* contributed guards still key on a literal argv[0] == "git". The shim still covers lefthook run from PATH.

Please pull before pushing again.

GitHub has not yet computed whether this branch merges cleanly with its base.


This review was drafted by an AI-assisted tool and
confirmed by an Apache Magpie maintainer. The maintainer
approving this PR has read the findings and signed off. If
something feels off, please reply on the PR and a maintainer
will follow up.

More on how Apache Magpie handles maintainer review:
Contributing guide.

main now carries the overcommit adapter (apache#1893). The shim dispatches on
all three install strategies (git-dir-flag, hooks-path-env and
hide-global-config), the registry validator accepts each, and the docs
and spec list prek, overcommit and lefthook together. The secure-setup
fingerprint is main's, restamped by the hook.

Generated-by: Claude Opus 5
@potiuk
potiuk merged commit 756bd5d into apache:main Oct 31, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

capability:platform Framework / agent substrate skills (install, verify, doctor, override, status, setup bootstrap) contract:hook-manager Tool capability: git-hook managers under the secure setup (install target, sandbox / host homes) family:ci .github workflows, prek, validators family:docs Docs, MISSION.md, READMEs family:setup setup-* skills family:tools tools/* substrate:action-guard Tool substrate: deterministic pre-tool-use command guards substrate:framework-dev Tool substrate: build / validate / eval the framework itself substrate:sandbox Tool substrate: agent isolation, egress control, settings audit

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hook-manager: add a lefthook adapter

3 participants