This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow-steward.git
The following commit(s) were added to refs/heads/main by this push:
new 3f42f8e fix(post-checkout): drop unrunnable /setup-steward slash
command (#200)
3f42f8e is described below
commit 3f42f8e18867af7f1545c3d40a062b75dfdad3bf
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sun May 17 21:55:15 2026 +0200
fix(post-checkout): drop unrunnable /setup-steward slash command (#200)
The post-checkout hook templates shipped in #184 (per-repo,
installed by /setup-steward adopt) and #199 (global, installed
under whole-user scope) both contained:
/setup-steward verify --auto-fix-symlinks || true
`/setup-steward verify` is a Claude Code slash command, not a
shell command. The hook fires in the operator's shell, where
there is no slash-command dispatcher. The shell tries to execute
the literal path /setup-steward, fails with "No such file or
directory", and || true swallows the exit code — but the stderr
line still prints on every git checkout. The global hook in #199
had a `command -v /setup-steward` guard so it was silent; the
per-repo template did not.
Fix: drop the steward-verify chunk from both templates entirely.
Symlink-drift reconciliation cannot be done from a shell hook;
it now happens lazily — the next time the operator opens Claude
Code in the worktree, the framework skills' pre-flight drift
check surfaces missing symlinks and prompts for the fix.
- adopt.md Step 10: hook template rewritten without the broken
line. Added a "Why no symlink reconciliation here" note
explaining the slash-command-from-shell issue and the lazy-
reconciliation alternative. Instructs adopters with the old
broken line in their installed hook to replace it.
- tools/agent-isolation/git-global-post-checkout.sh: same drop.
The previous `command -v /setup-steward` guard masked the bug
but encoded a wrong design. Updated header comment to explain
why the apache-steward reconciliation chunk is intentionally
absent.
- unadopt.md: two stale references updated. The inventory table
row and the removal-procedure description for the post-checkout
hook both now describe the current (post-fix) hook content.
The removal procedure explicitly handles the obsolete-line case.
Generated-by: Claude Code (Opus 4.7)
---
.claude/skills/setup-steward/adopt.md | 42 +++++++++++++-------
.claude/skills/setup-steward/unadopt.md | 17 +++++---
tools/agent-isolation/git-global-post-checkout.sh | 48 ++++++++++-------------
3 files changed, 60 insertions(+), 47 deletions(-)
diff --git a/.claude/skills/setup-steward/adopt.md
b/.claude/skills/setup-steward/adopt.md
index 05e1067..1a56ce8 100644
--- a/.claude/skills/setup-steward/adopt.md
+++ b/.claude/skills/setup-steward/adopt.md
@@ -541,10 +541,9 @@ collected values substituted in (leaving any unanswered
field as
## Step 10 — Worktree-aware post-checkout hook (FRESH only)
-Install `<repo-root>/.git/hooks/post-checkout` that re-creates
-the gitignored symlinks if a fresh worktree is checked out
-**and** chains into the sandbox-allowlist helper installed by
-`setup-isolated-setup-install` so the new worktree's working
+Install `<repo-root>/.git/hooks/post-checkout` that chains into
+the sandbox-allowlist helper installed by
+`setup-isolated-setup-install`, so the new worktree's working
directory is added to the worktree's own
`.claude/settings.local.json`'s `sandbox.filesystem.allowRead` /
`allowWrite` (defensive against
@@ -552,28 +551,24 @@ directory is added to the worktree's own
— see
[`setup-isolated-setup-install/SKILL.md` → Step
P](../setup-isolated-setup-install/SKILL.md#step-p--project-root-coverage-in-the-sandbox-allowlists)).
-The hook is a small shell script, not a one-liner. Surface the
-exact content to the user before writing:
+The hook is a small shell script. Surface the exact content to
+the user before writing:
```bash
#!/usr/bin/env bash
# apache-steward post-checkout hook (installed by /setup-steward adopt).
-# Two responsibilities, each idempotent:
-# 1. Reconcile gitignored framework-skill symlinks for the
-# current worktree.
-# 2. Add the current worktree's working dir to the worktree's
-# own .claude/settings.local.json's sandbox allowlists
-# (per issue #197) — chains into the helper if installed by
-# /setup-isolated-setup-install, no-op when absent.
+# Add the current worktree's working dir to the worktree's own
+# .claude/settings.local.json sandbox allowlists (per issue #197).
+# Chains into the helper if installed by /setup-isolated-setup-install;
+# no-op when the helper is absent.
set -u
-/setup-steward verify --auto-fix-symlinks || true
if [ -x "$HOME/.claude/scripts/sandbox-add-project-root.sh" ]; then
"$HOME/.claude/scripts/sandbox-add-project-root.sh" || true
fi
exit 0
```
-The `|| true` guards keep the hook from failing the surrounding
+The `|| true` guard keeps the hook from failing the surrounding
git operation (`git checkout`, `git worktree add`) — the hook is
best-effort reconciliation, not a gate.
@@ -582,6 +577,23 @@ the helper-script line is a no-op (the `-x` test fails).
When
they later install the secure setup, no hook re-write is needed:
the next `post-checkout` fires the helper automatically.
+**Why no framework-skill symlink reconciliation here.** Earlier
+template versions of this hook also called
+`/setup-steward verify --auto-fix-symlinks` to recreate
+gitignored symlinks after a checkout. That line printed a spurious
+`No such file or directory` error on every `git checkout` because
+`/setup-steward` is a **Claude Code slash command**, not a shell
+command, and the hook fires in the operator's shell where there is
+no slash-command dispatcher. The line has been removed.
+Symlink-drift reconciliation now happens **lazily** — the next
+time the operator opens Claude Code in the worktree, the framework
+skills' pre-flight drift check surfaces any missing symlinks and
+`/setup-steward verify` (or any skill that needs the symlink)
+prompts for the fix. Adopters whose existing hooks still contain
+the broken line should remove it; the
+[`setup-isolated-setup-update`](../setup-isolated-setup-update/SKILL.md)
+drift check surfaces stale hook content on a routine sweep.
+
## Step 11 — Project doc updates (FRESH only)
Update two adopter-facing docs so contributors discover the
diff --git a/.claude/skills/setup-steward/unadopt.md
b/.claude/skills/setup-steward/unadopt.md
index cd8cbf1..e04005a 100644
--- a/.claude/skills/setup-steward/unadopt.md
+++ b/.claude/skills/setup-steward/unadopt.md
@@ -89,7 +89,7 @@ every artefact).
| Committed lock | `<committed-lock>` | exists |
| `.gitignore` entries | `<repo-root>/.gitignore` | which of the entries from
[`adopt.md` Step 7](adopt.md) are present |
| Framework-skill symlinks | `<adopter-skills-dir>/` (and `.github/skills/` if
double-symlinked) | each symlink whose target resolves into
`<snapshot-dir>/.claude/skills/` |
-| Post-checkout hook | `<repo-root>/.git/hooks/post-checkout` | exists +
contains `/setup-steward verify --auto-fix-symlinks` |
+| Post-checkout hook | `<repo-root>/.git/hooks/post-checkout` | exists +
invokes `~/.claude/scripts/sandbox-add-project-root.sh` |
| Doc section: `README.md` | `<repo-root>/README.md` | contains the `##
Agent-assisted contribution (apache-steward)` heading |
| Doc section: `AGENTS.md` | `<repo-root>/AGENTS.md` | contains the `##
apache-steward framework` heading |
| Doc section: `CONTRIBUTING.md` | `<repo-root>/CONTRIBUTING.md` | contains
the adoption section (fallback layout) |
@@ -193,10 +193,17 @@ pointing at a deleted snapshot.
touch a non-symlink at the same path.
2. **Post-checkout hook.** Remove only if its content matches
the steward recipe verbatim (i.e. the hook the adopt flow
- wrote — a single `/setup-steward verify --auto-fix-symlinks`
- invocation). If the hook contains additional adopter logic,
- surface that, leave the hook in place, and tell the user
- which line to delete by hand.
+ wrote — a single
+ `~/.claude/scripts/sandbox-add-project-root.sh` invocation
+ guarded by the `-x` test; see
+ [`adopt.md` Step
10](adopt.md#step-10--worktree-aware-post-checkout-hook-fresh-only)
+ for the exact text). If the hook contains additional adopter
+ logic, surface that, leave the hook in place, and tell the
+ user which line to delete by hand. Hooks that still contain
+ the obsolete `/setup-steward verify --auto-fix-symlinks` line
+ (a Claude Code slash command that does not work from a shell
+ hook — removed in a later framework release) should be
+ replaced with the current Step 10 template.
3. **Snapshot directory.** `rm -rf <snapshot-dir>/`.
4. **Local lock.** `rm <local-lock>`.
5. **`.gitignore` entries.** Read `<repo-root>/.gitignore`,
diff --git a/tools/agent-isolation/git-global-post-checkout.sh
b/tools/agent-isolation/git-global-post-checkout.sh
index ba2990f..f3b4b67 100755
--- a/tools/agent-isolation/git-global-post-checkout.sh
+++ b/tools/agent-isolation/git-global-post-checkout.sh
@@ -29,28 +29,28 @@
# across the operator's host invokes this script — for any repo,
# not just apache-steward adopters.
#
-# Two responsibilities, both best-effort + idempotent + `|| true`
-# so the hook never breaks the surrounding git operation:
+# One responsibility, best-effort + idempotent + `|| true` so the
+# hook never breaks the surrounding git operation:
#
-# 1. **apache-steward symlink reconciliation.** If the working
-# tree carries `.apache-steward.lock`, this is a steward-
-# adopted repo and its gitignored framework-skill symlinks
-# may need re-creating after a checkout (the symlinks point
-# into `.apache-steward/`, which is itself gitignored). The
-# hook calls `/setup-steward verify --auto-fix-symlinks` if
-# that command is on PATH; if not, it falls through silently
-# (the operator may not be in a Claude Code session, or may
-# not have the framework installed beyond this hook).
+# **Sandbox-allowlist for the current worktree.** If the working
+# tree has a `.claude/` directory (i.e. it is Claude-Code-aware)
+# and the framework's `sandbox-add-project-root.sh` helper is
+# installed at `~/.claude/scripts/`, the hook calls the helper
+# to populate `<worktree>/.claude/settings.local.json` with the
+# worktree's absolute path. Defensive against the harness
+# behaviour documented at
+# https://github.com/apache/airflow-steward/issues/197 .
#
-# 2. **Sandbox-allowlist for the current worktree.** If the
-# working tree has a `.claude/` directory (i.e. it is
-# Claude-Code-aware) and the framework's
-# `sandbox-add-project-root.sh` helper is installed at
-# `~/.claude/scripts/`, the hook calls the helper to populate
-# `<worktree>/.claude/settings.local.json` with the
-# worktree's absolute path. Defensive against the harness
-# behaviour documented at
-# https://github.com/apache/airflow-steward/issues/197 .
+# **Not** in this hook: apache-steward symlink reconciliation.
+# `/setup-steward verify --auto-fix-symlinks` is a Claude Code
+# slash command, not a shell command, so it cannot be invoked from
+# a `git checkout` hook running in the operator's shell — the
+# previous template version of this hook spelled out the line
+# anyway and printed a spurious "No such file or directory" error
+# on every checkout. Symlink-drift in steward-adopted repos is now
+# reconciled **lazily** — the next time the operator opens Claude
+# Code in the worktree, `/setup-steward verify` detects any drift
+# and offers to fix it.
#
# IMPORTANT — `core.hooksPath` shadowing. When `core.hooksPath` is
# set globally, git looks ONLY in that directory for hooks. Every
@@ -78,13 +78,7 @@ if [ -z "$worktree" ]; then
exit 0
fi
-# 1. apache-steward symlink reconciliation
-if [ -f "$worktree/.apache-steward.lock" ] \
- && command -v /setup-steward >/dev/null 2>&1; then
- /setup-steward verify --auto-fix-symlinks 2>/dev/null || true
-fi
-
-# 2. Sandbox-allowlist helper
+# Sandbox-allowlist helper
if [ -x "$HOME/.claude/scripts/sandbox-add-project-root.sh" ] \
&& [ -d "$worktree/.claude" ]; then
"$HOME/.claude/scripts/sandbox-add-project-root.sh" 2>/dev/null || true