fix(init): chmod +x workspace hooks in OVERRIDE mode too (#359)

`agnes init` ran `_chmod_workspace_hooks(workspace)` only in DEFAULT
mode (inside `if not override_active:`). The OVERRIDE path — used for
Initial-Workspace-Template seed-repo flows where the admin's template
repo is cloned into the workspace — silently skipped chmod.

When the seed repo's git checkout didn't preserve the +x bit
(filemode=false, archive extractions, FUSE/NFS mounts), hooks like
`.claude/hooks/skill-nudge/nudge.sh` and
`.claude/hooks/prompt-history/log-prompt.sh` landed non-executable
and every SessionStart fired `Permission denied`. The chmod helper
itself already recurses (`rglob`) so the subdir-scoped hook layouts
were covered — the bug was the call site.

Moved the call out of the override gate to a common step before the
first pull. Both DEFAULT and OVERRIDE paths now chmod hooks before
the first SessionStart can fire.
This commit is contained in:
Vojtech 2026-05-19 20:45:45 +04:00 committed by GitHub
parent a8fe4e8b21
commit 0c9e172ce1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 31 additions and 1 deletions

View file

@ -10,6 +10,21 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
## [Unreleased]
### Fixed
- `agnes init` now runs `_chmod_workspace_hooks(workspace)` for OVERRIDE
mode too (Initial Workspace Template seed-repo flow), not just the
DEFAULT path. Override-mode workspaces seeded from an admin's
template repo were leaving hooks like
`.claude/hooks/skill-nudge/nudge.sh` and
`.claude/hooks/prompt-history/log-prompt.sh` non-executable when
the seed repo's git checkout didn't preserve the +x bit
(`core.filemode=false`, archive extractions, FUSE/NFS mounts), and
every SessionStart fired `Permission denied`. The chmod helper
already recurses (`rglob`) so subdir-scoped hook layouts were
covered — the bug was that the call site sat inside the
`if not override_active:` block. Moved out to a common step
before the first pull so every init path runs it.
## [0.55.4] — 2026-05-19
### Security

View file

@ -493,7 +493,6 @@ def init(
), encoding="utf-8")
install_claude_hooks(workspace)
install_claude_commands(workspace)
_chmod_workspace_hooks(workspace)
# ------------------------------------------------------------------
# Step 6: CLAUDE.local.md stub — only when absent. `--force` does NOT
@ -511,6 +510,22 @@ def init(
encoding="utf-8",
)
# ------------------------------------------------------------------
# Always chmod +x hook scripts that landed on disk, regardless of
# which path seeded the workspace. In DEFAULT mode the hooks come
# from `install_claude_hooks` above; in OVERRIDE mode they come
# from the admin's initial-workspace-template clone — and `git
# checkout` of that template doesn't reliably preserve the +x bit
# (filemode=false repos, archive extractions, FUSE/NFS mounts),
# so hooks like `.claude/hooks/skill-nudge/nudge.sh` or
# `.claude/hooks/prompt-history/log-prompt.sh` could land non-
# executable and fire `Permission denied` on the very next
# SessionStart. `_chmod_workspace_hooks` recurses (`rglob`) so
# subdir-scoped hook layouts are covered. Best-effort, no-op on
# Windows NTFS.
# ------------------------------------------------------------------
_chmod_workspace_hooks(workspace)
# ------------------------------------------------------------------
# Step 7: first pull. `run_pull` records per-stage failures inside
# `result.errors` rather than raising for transient issues, so any