Skip to content

[Bugfix #693] Restore executable bit on forge scripts after install#696

Merged
waleedkadous merged 6 commits into
mainfrom
builder/bugfix-693
Apr 23, 2026
Merged

[Bugfix #693] Restore executable bit on forge scripts after install#696
waleedkadous merged 6 commits into
mainfrom
builder/bugfix-693

Conversation

@waleedkadous
Copy link
Copy Markdown
Contributor

@waleedkadous waleedkadous commented Apr 22, 2026

Summary

Fixes #693afx spawn failing with Permission denied on fresh installs because forge concept scripts (scripts/forge/<provider>/*.sh) ship as 0644.

Root Cause

pnpm pack / pnpm publish strips executable bits from every file outside the bin field, even when the source is tracked in git with mode 100755. The scripts at packages/codev/scripts/forge/**/*.sh are all 100755 in the repo (confirmed via git ls-files -s), but the produced tarball has them as 0644:

# pnpm pack tarball contents
-rw-r--r--  0 ...  package/scripts/forge/github/issue-view.sh

npm pack on the same tree preserves the bits, so this is a pnpm-specific packaging bug rather than a git state issue.

The issue's suggested fix (chmod +x + commit) does not help — the files are already 100755 in git.

Fix

Move the existing one-liner postinstall into a dedicated packages/codev/scripts/postinstall.mjs that chmods every scripts/forge/<provider>/*.sh back to 0755. The pre-existing node-pty spawn-helper chmod is preserved. scripts/postinstall.mjs is added to files so it ships in the tarball.

Implementation uses readdirSync(..., { withFileTypes: true }) so a broken symlink in the provider dir wouldn't crash postinstall.

This is robust against both pnpm-packed and npm-packed tarballs, and does not require changing the release workflow.

Verification

cd packages/codev && pnpm pack
# Install the tarball into a scratch dir — postinstall runs:
cd /tmp/scratch && npm install /path/to/cluesmith-codev-3.0.0-rc.11.tgz --no-save
ls -la node_modules/@cluesmith/codev/scripts/forge/github/issue-view.sh
# -rwxr-xr-x ...

All 42 forge scripts (github/16, gitlab/14, gitea/12) end up at 0755.

Test Plan

  • New regression test: packages/codev/src/__tests__/bugfix-693-forge-exec-bit.test.ts
    • Verifies package.json wires postinstall + ships the script via files.
    • Runs postinstall.mjs against a fixture tree of mode-0644 scripts and asserts they become 0755.
    • Asserts every committed forge script lives at scripts/forge/<provider>/<name>.sh (one level deep), so new scripts are not silently missed.
  • Full suite: pnpm vitest runTest Files 126 passed (126), Tests 2523 passed | 13 skipped (2536)
  • Manual verification: packed with pnpm pack, installed into a scratch dir, confirmed -rwxr-xr-x on all forge scripts.

CMAP Review

  • Claude: APPROVE (HIGH) — "Clean, well-scoped bugfix with correct root cause analysis, proper ESM postinstall implementation, and solid regression tests."
  • Codex: APPROVE (MEDIUM) — "Cleanly fixes the packaged forge-script executable-bit issue with a focused postinstall hook and solid regression coverage."
  • Gemini: REQUEST_CHANGES (HIGH) → addressed in follow-up commit. Gemini flagged a correctness nit: the original statSync loop could throw on a broken symlink and crash postinstall. Fixed by switching to readdirSync(..., { withFileTypes: true }) (commit 6779092). Gemini's other two "issues" (missing codev/reviews/ doc, status.yaml still at investigate) don't apply: the BUGFIX protocol keeps the review in the PR body, and status.yaml is now advanced to verified via porch done — builders aren't allowed to edit it directly.

pnpm pack/publish strips executable bits from every file outside the
`bin` field, so `scripts/forge/<provider>/*.sh` ship as 0644 on fresh
installs. `afx spawn` then fails with `Permission denied` when
`executeForgeCommand` invokes `issue-view` (and every other concept
routed through the shell dispatcher).

Fix by moving the existing one-liner postinstall into a dedicated
`scripts/postinstall.mjs` that also chmods every forge script back to
0755 after install. The node-pty spawn-helper fix is preserved.
Gemini PR review flagged a correctness nit: the original code called
statSync on each subdir entry, which throws on broken symlinks and would
crash postinstall. readdirSync(..., { withFileTypes: true }) gives the
same info in one syscall without the throw path, and is more idiomatic.

Also drops the unused count return value.
@waleedkadous waleedkadous merged commit c766328 into main Apr 23, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Forge scripts ship without executable bit — blocks afx spawn on fresh installs

1 participant