fix(marketplace): chmod +x .sh files after fetch+reset, not just bootstrap (#352)
Devin Review #350 caught a coverage gap: the chmod +x pass only ran in _bootstrap_clone (initial install), not in _git_fetch_and_reset (every subsequent `agnes refresh-marketplace` and `--check` follow-up). On core.filemode=false setups, a `git reset --hard FETCH_HEAD` overwrites the working tree without restoring the +x bit, so a hook plugin version bump would silently re-strip the bit and Permission-denied breakage would return on the next SessionStart. Extracted _chmod_clone_sh_files() helper; both _bootstrap_clone and _git_fetch_and_reset now call it. Best-effort, no-op on Windows NTFS.
This commit is contained in:
parent
fc6de77e06
commit
318802854c
2 changed files with 53 additions and 28 deletions
37
CHANGELOG.md
37
CHANGELOG.md
|
|
@ -10,6 +10,27 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- `agnes refresh-marketplace` (non-bootstrap path) now re-applies
|
||||
`chmod +x` to every `.sh` under `~/.agnes/marketplace` after each
|
||||
`git reset --hard FETCH_HEAD`, not just on the initial bootstrap
|
||||
clone. `git reset --hard` rewrites the working tree from the tree
|
||||
object — if the upstream tree stores a hook script as non-
|
||||
executable (or on `core.filemode=false` setups), every refresh
|
||||
silently re-strips the +x bit and the previously-fixed hooks fire
|
||||
with "Permission denied" again on the next `SessionStart`.
|
||||
Extracted `_chmod_clone_sh_files()` helper, called from both
|
||||
`_bootstrap_clone` and `_git_fetch_and_reset`. Best-effort, no-op
|
||||
on Windows NTFS. Closes the coverage gap Devin Review flagged on
|
||||
PR #350.
|
||||
- Stripped six stale unresolved merge-conflict markers
|
||||
(`<<<<<<<` / `=======` / `>>>>>>>`) from the `[0.55.1]` section of
|
||||
`CHANGELOG.md` that landed on `main` via PR #350's release-cut
|
||||
commit. Markers were rendering as raw conflict text on GitHub and
|
||||
in any tooling that parses the changelog; the HEAD-side content
|
||||
inside each pair is what was kept (the incoming side held
|
||||
superseded intermediate-commit duplicates).
|
||||
|
||||
## [0.55.2] — 2026-05-19
|
||||
|
||||
### Fixed
|
||||
|
|
@ -205,7 +226,6 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
|
|||
save) mirroring the Memory Domain pattern.
|
||||
|
||||
### Changed
|
||||
<<<<<<< HEAD
|
||||
- **Bulk-assign tables → package** modal — package dropdown options
|
||||
now carry a `(N of M tables already in)` suffix so admins see the
|
||||
existing distribution before picking a target. Counts surface
|
||||
|
|
@ -515,8 +535,6 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
|
|||
- Single PR cutover (no two-phase rollout). Legacy
|
||||
`marketplace_plugins.is_system` + `user_plugin_optouts` retained
|
||||
per spec D1 — Marketplace was deliberately not touched.
|
||||
=======
|
||||
<<<<<<< HEAD
|
||||
- /home onboarding Step 2 retitled "turn on permission-skip for setup"
|
||||
and now leads with `claude --dangerously-skip-permissions` as the
|
||||
recommended session flag, because the Step 4 paste runs ~20 shell
|
||||
|
|
@ -524,7 +542,6 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
|
|||
The flag is session-scoped, drops on next plain `claude`. Auto-accept
|
||||
via Shift + Tab kept as the strict-review fallback for users who want
|
||||
to approve each command; persistent YOLO setup link unchanged.
|
||||
>>>>>>> 4c4e9e42 (fix(web): swap /home Steps 2↔3, claude --yolo as copy-button command)
|
||||
|
||||
## [0.54.29] — 2026-05-19
|
||||
|
||||
|
|
@ -579,18 +596,6 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
|
|||
UI shows the corrected project). The orchestrator now compares the
|
||||
two at every rebuild and, if they differ, calls
|
||||
`rebuild_from_registry()` to regenerate the extract.
|
||||
=======
|
||||
- /home onboarding reordered: folder creation is now Step 2 (was
|
||||
Step 3) and starting Claude with `claude --dangerously-skip-permissions`
|
||||
is the new Step 3 (was the auto-mode step), rendered with the same
|
||||
`.install-cmd` + copy-button affordance the other steps use. Step 4
|
||||
paste runs ~20 shell commands that auto-accept-edits would not cover
|
||||
(Bash still prompts), so the YOLO flag is the default recommendation;
|
||||
session-scoped, drops on next plain `claude`. Shift + Tab → auto-
|
||||
accept-edits kept as the strict-review fallback; persistent YOLO
|
||||
allowlist link to /setup-advanced#yolo unchanged. Setup script's
|
||||
"Verify cwd" warning copy refreshed to reference "/home Step 2".
|
||||
>>>>>>> c195e0fa (fix(web): swap /home Steps 2↔3, claude --yolo as copy-button command)
|
||||
- Setup script no longer auto-creates the workspace folder. Step 2 of
|
||||
the pasted prompt now runs `pwd`, compares it to `$HOME/<workspace_dir>`
|
||||
(the folder the /home page's visible Step 3 told the user to create
|
||||
|
|
|
|||
|
|
@ -242,18 +242,7 @@ def _bootstrap_clone(token: str) -> bool:
|
|||
except OSError:
|
||||
pass
|
||||
|
||||
# Add execute bit to every `.sh` under the clone — git's checkout doesn't
|
||||
# always preserve the file-mode bit (filemode=false repos, archive
|
||||
# extractions), and Claude Code's later `plugin install` copies the
|
||||
# files into the workspace `.claude/hooks/` AS-IS, so hooks that lost
|
||||
# the +x bit here would fire with Permission denied. Fixing at the
|
||||
# source (marketplace clone) means every downstream plugin install
|
||||
# gets executable hooks for free. Best-effort: no-op on Windows NTFS.
|
||||
for sh in CLONE_DIR.rglob("*.sh"):
|
||||
try:
|
||||
sh.chmod(sh.stat().st_mode | 0o111)
|
||||
except OSError:
|
||||
pass
|
||||
_chmod_clone_sh_files()
|
||||
|
||||
if not _register_clone_with_claude(CLONE_DIR):
|
||||
return False
|
||||
|
|
@ -444,12 +433,41 @@ def _local_head_sha() -> Optional[str]:
|
|||
return sha or None
|
||||
|
||||
|
||||
def _chmod_clone_sh_files() -> None:
|
||||
"""Add execute bit to every `.sh` under CLONE_DIR.
|
||||
|
||||
Git's checkout doesn't always preserve the file-mode bit (filemode=false
|
||||
repos, archive extractions, FUSE/NFS mounts with filemode detection
|
||||
disabled), and Claude Code's later `plugin install` copies the files
|
||||
into the workspace `.claude/hooks/` AS-IS, so hooks that lost the +x
|
||||
bit here would fire with Permission denied. Fixing at the source
|
||||
(marketplace clone) means every downstream plugin install gets
|
||||
executable hooks for free.
|
||||
|
||||
Called from both `_bootstrap_clone` (after the initial `git clone`)
|
||||
and `_git_fetch_and_reset` (after every `git reset --hard FETCH_HEAD`
|
||||
on subsequent refreshes) so a version bump that touches a `.sh`
|
||||
can't silently strip the bit. Best-effort: no-op on Windows NTFS,
|
||||
swallows per-file errors.
|
||||
"""
|
||||
for sh in CLONE_DIR.rglob("*.sh"):
|
||||
try:
|
||||
sh.chmod(sh.stat().st_mode | 0o111)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def _git_fetch_and_reset(token: str) -> bool:
|
||||
"""Fetch from origin then hard-reset to FETCH_HEAD.
|
||||
|
||||
Not `pull --ff-only`: the marketplace bare repo on the server rebuilds
|
||||
as a fresh orphan commit on every content change, so two snapshots
|
||||
have unrelated histories and fast-forward is impossible.
|
||||
|
||||
After a successful reset, re-applies the `.sh` execute bit across the
|
||||
clone — `git reset --hard` overwrites working-tree files according to
|
||||
the repo's `core.filemode` setting, and on systems where that is
|
||||
`false` the bit gets stripped silently.
|
||||
"""
|
||||
if not _git_fetch_only(token):
|
||||
return False
|
||||
|
|
@ -465,6 +483,8 @@ def _git_fetch_and_reset(token: str) -> bool:
|
|||
typer.echo(reset.stderr, err=True)
|
||||
return False
|
||||
|
||||
_chmod_clone_sh_files()
|
||||
|
||||
if reset.stdout:
|
||||
typer.echo(reset.stdout.rstrip())
|
||||
return True
|
||||
|
|
|
|||
Loading…
Reference in a new issue