Skip to content

fix: launch git-hook TS scripts with node --import tsx - #2597

Merged
gvladika merged 6 commits into
mainfrom
fac/exsc-1219
Nov 9, 2026
Merged

gvladika merged 6 commits into
mainfrom
fac/exsc-1219

Conversation

@gvladika

@gvladika gvladika commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Which Linear task belongs to this PR?

EXSC-1219

Why did I implement it this way?

Why it is implemented this way

The tsx CLI (whether launched as bunx tsx, npx tsx or a global tsx) opens a Unix-socket IPC server ($TMPDIR/tsx-<uid>/<pid>.pipe) before it runs the script. In sandboxes that deny socket binding, everything a git hook launches that way exits with listen EPERM: the two lint-staged fences block every .ts/.js commit, and the pre-push validator blocks every push. node --import tsx runs the same script on Node through the tsx loader, which opens no socket.

The diff:

  • package.json: the lint-staged entries for script/deploy/safe/funnel-fence.ts and script/utils/node-runtime-fence.ts start with node --import tsx. They are still the third and fourth steps, after prettier --write and oxlint and before typecheck-files.sh. lint:funnel and lint:node-runtime are changed the same way and are still exactly the lint-staged command plus . and script tasks.
  • .husky/pre-push: validateScripts.ts launches with node --import tsx.
  • enforceProposalFunnel.yml, validateScripts.yml: a setup-node step (Node 22) before the fence runs. node --import needs Node ≥20.6, and on a runner with no Node, bun run puts its own node shim on PATH and would run the fence on Bun.
  • Each fence test file gets one test that spawns the lint-staged command with TMPDIR set to a path longer than a Unix socket path may be (104 bytes on macOS, 108 on Linux) and asserts a clean run. Any launcher that binds a socket fails it: with the entries switched back to bunx tsx, both tests fail with listen EINVAL.
  • .agents/rules/200-typescript.md: the launcher exception covers what git hooks run, and names the tsx CLI (not bunx) as what opens the socket.

How each criterion is met:

  • Order: the existing "runs after the fixers and before the type check" test checks it.
  • Lockstep: the existing lint:funnel / lint:node-runtime equality tests check it.
  • Runs without a bindable socket: the new tests check it.
  • Commit still refused on a violation: the existing "fires through the lint-staged entry" tests spawn the new command on a violating file and assert the refusal message.

Run locally:

  • bun test script/deploy/safe/propose-funnel-fence.test.ts script/utils/node-runtime-fence.test.ts script/utils/validateScriptsWorkflow.test.ts: all pass.
  • node --import tsx ./script/utils/validateScripts.ts --base origin/main --head HEAD with an unbindable TMPDIR: passes.
  • bash script/utils/typecheck-files.sh, bunx oxlint --type-aware, bunx prettier --check on the changed files, and actionlint on the two workflows: clean.
  • The pre-commit and pre-push hooks ran the new commands on the last push.

The other bunx tsx package scripts are left as they are, as the ticket specifies.

Checklist before requesting a review

Checklist for reviewer (DO NOT DEPLOY and contracts BEFORE CHECKING THIS!!!)

  • I have checked that any arbitrary calls to external contracts are validated and or restricted
  • I have checked that any privileged calls (i.e. storage modifications) are validated and or restricted
  • I have ensured that any new contracts have had AT A MINIMUM 1 preliminary audit conducted on by <company/auditor>

🤖 Generated with Claude Code

bunx tsx opens an IPC socket before running the script, so the
pre-commit fences die with listen EPERM wherever socket binding is
denied. node --import tsx runs the same script on Node without it.

Refs: EXSC-1219

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 44 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: bc22f158-7f71-4fc1-b6fe-9bb8732917fc
Commits

Reviewing files that changed from the base of the PR and between bd7e68b and 3816f27.

Files selected for processing: 4
  • .github/workflows/enforceProposalFunnel.yml
  • .github/workflows/validateScripts.yml
  • script/deploy/safe/propose-funnel-fence.test.ts
  • script/utils/node-runtime-fence.test.ts

No actionable comments were generated in the recent review.

Walkthrough

The lint fences and pre-push validator now use node --import tsx. Workflows set up Node 22. Integration tests run the configured commands with a long TMPDIR path.

Changes

Fence command execution

Layer / File(s) Summary
Configure Node-based launchers
.agents/rules/200-typescript.md, package.json, .husky/pre-push, .github/workflows/enforceProposalFunnel.yml, .github/workflows/validateScripts.yml
The lint fences, staged commands, and pre-push validator use node --import tsx. Both workflows set up Node 22 before Bun.
Test commands with long TMPDIR
script/deploy/safe/propose-funnel-fence.test.ts, script/utils/node-runtime-fence.test.ts
The tests run configured commands with a long TMPDIR path. They check for successful execution and, in the Node-runtime test, absence of a listen error.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Possibly related PRs

Merge Risk: Minimal · previous review e9262

The launchers have the required Node version and dependency. No merge-blocking issue was identified.

Pre-merge checks: Passed 5
Check name Status Explanation
Description check Passed The description includes the Linear task, implementation rationale, detailed change summary, test coverage, validation results, and the required checklists. Reviewer-only checklist items are appropria…
Docstring Coverage Passed No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title "fix: launch git-hook TS scripts with node --import tsx" matches the main change. The PR switches the lint fences, the pre-push validator, and the related package scripts from bunx tsx to …
Full details: Description check

Explanation

The description includes the Linear task, implementation rationale, detailed change summary, test coverage, validation results, and the required checklists. Reviewer-only checklist items are appropriately left for reviewer completion.

Full details: Docstring Coverage

Explanation

No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 2 files. (4 skipped: 4 unsupported.)

Full details: Title check

Explanation

The title "fix: launch git-hook TS scripts with node --import tsx" matches the main change. The PR switches the lint fences, the pre-push validator, and the related package scripts from bunx tsx to node --import tsx. The stated reason is to avoid the tsx CLI's Unix-socket listener, which can fail with listen EPERM in restricted sandboxes. The title is specific and concise, and it uses the conventional fix: prefix. It does not name the sandbox or socket cause, but it does not need to cover every detail.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share
Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 472a4382-9790-4d32-8929-a97fbd3a6255
Commits

Reviewing files that changed from the base of the PR and between e668692 and e9262d8.

Files selected for processing: 6
  • .agents/rules/200-typescript.md
  • .github/workflows/enforceProposalFunnel.yml
  • .github/workflows/validateScripts.yml
  • .husky/pre-push
  • script/deploy/safe/propose-funnel-fence.test.ts
  • script/utils/node-runtime-fence.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 0 remain after this review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gvladika
gvladika marked this pull request as ready for review October 28, 2026 09:19
@gvladika
gvladika requested review from a team October 28, 2026 09:19
@gvladika gvladika added the Agent Review Request triggers QA Agent Zeus label Oct 28, 2026
@github-actions github-actions Bot added requires-types Trigger Types Bindings CI (ABI/type generation for lifi-contract-types) QA AI Reviewing Zeus QA review in progress and removed Agent Review Request triggers QA Agent Zeus labels Oct 28, 2026
lifi-qa-agent[bot]
lifi-qa-agent Bot previously approved these changes Oct 28, 2026

@lifi-qa-agent lifi-qa-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 [Automated QA Review] Approved — PR #2597: pre-commit fences now launch via node --import tsx; lockstep scripts, ordering and tests verified, no findings

@lifi-qa-agent lifi-qa-agent Bot added QA AI Approved Zeus QA review passed and removed QA AI Reviewing Zeus QA review in progress labels Oct 28, 2026
@gvladika
gvladika marked this pull request as draft October 28, 2026 10:51
@gvladika

Copy link
Copy Markdown
Contributor Author

The independent review found 1 minor nit. None is fixed unless you pick it.

  1. script/deploy/safe/propose-funnel-fence.test.ts:229 The diff adds a new test to each fence test file; the ticket only requires the existing tests to hold unedited. Existing tests are untouched, so this is a small scope expansion and a useful regression guard.
    Proposed fix: Optional: keep, or drop if no test-file change is wanted.

Reply fix 1, 3 to have those fixed, fix all, or skip. Anything else you write is handled as ordinary review feedback.

The pre-push hook ran validateScripts.ts through the tsx CLI, which binds
an IPC socket under $TMPDIR, so pushes failed in sandboxes that deny
socket listen. The fence tests now spawn the staged command with a TMPDIR
too long for a socket path instead of pinning the command string, and
the two CI fence jobs set up Node 22 so `node --import` never runs on an
older runner Node or on Bun's node shim.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gvladika
gvladika marked this pull request as ready for review October 28, 2026 14:23
@gvladika gvladika changed the title fix: launch the pre-commit lint fences with node --import tsx fix: launch git-hook TS scripts with node --import tsx Oct 28, 2026
@gvladika
gvladika enabled auto-merge (squash) October 28, 2026 14:27
lifi-qa-agent[bot]
lifi-qa-agent Bot previously approved these changes Oct 28, 2026

@lifi-qa-agent lifi-qa-agent Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 [Automated QA Review] Approved — PR #2597: full report in the Zeus PR comment.

@lifi-qa-agent

lifi-qa-agent Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

🔍 QA Review — EXSC-1219 · PR #2597 (🔁 Re-review, head 3816f27)

Verdict: ✅ Approve — 0 findings · Awaiting response: 0 · 0 refuted

Delta since the last approval (b1ad022 → 3816f27, 1 commit; 2 files): the two "no IPC socket" regression tests address the reviewer's nits. No source, workflow or hook file changed.

Resolution status of the prior review

The prior review had 0 findings. The two reviewer nits that dismissed an earlier approval are both fixed:

  • ✅ propose-funnel-fence.test.ts and node-runtime-fence.test.ts: the TMPDIR segment is now 200 characters, so the test also fails on macOS with a recent Node (which binds socket paths up to about 250 bytes). The comment states the 108-byte Linux limit and the macOS behaviour.
  • ✅ node-runtime-fence.test.ts now asserts the 1 file(s) checked, 0 refused line, as the funnel test does. It sets NODE_ENV=development for that child only, because bun test sets NODE_ENV=test and consola drops the success line under it. The shared spawnEnv is left alone.

What was verified

  • The 200-character directory is mkdtemp'd under tmpdir() and removed in finally; a 200-character component stays under the filesystem's 255-byte name limit.
  • The added assertion matches the fence's real output text and does not weaken the existing not.toContain('listen') check.
  • CI on the head passes (run-ts-tests, validate-scripts, enforce-proposal-funnel, CodeQL, Aikido). protect-critical-code is red; it is the maintainer-approval gate.

Findings

None.

Notes (do not gate)

Downstream impact

Tests only in this delta; the PR itself touches the git-hook fence launchers, two CI workflows and docs. No src/ change, so no audit entry. No sibling PRs.

thesems
thesems previously approved these changes Nov 2, 2026

@thesems thesems 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.

LGTM. One thing to sort out on rebase: #2607 moves every workflow to setup-node with .nvmrc (Node 26). This PR adds a node-version: 22 step in enforceProposalFunnel.yml and validateScripts.yml. If it lands second, those jobs run on Node 22 while engines says >=26. Suggest dropping the step here, or using node-version-file: .nvmrc.

gvladika and others added 2 commits November 3, 2026 09:12
@gvladika
gvladika dismissed stale reviews from thesems and lifi-qa-agent[bot] via b1ad022 November 3, 2026 08:38
@gvladika

gvladika commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — switched both Set up Node steps to node-version-file: .nvmrc in b1ad022 (kept the step, since the fence launches with node --import). Whichever of this and #2607 lands second should drop the duplicate Set up Node step in enforceProposalFunnel.yml / validateScripts.yml, keeping it before Set up Bun.

lifi-qa-agent[bot]
lifi-qa-agent Bot previously approved these changes Nov 3, 2026

@lifi-qa-agent lifi-qa-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 [Automated QA Review] Approved — PR #2597: the new setup-node steps give the node --import fences a pinned Node from .nvmrc

thesems
thesems previously approved these changes Nov 3, 2026

@thesems thesems 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.

LGTM. The Node-version follow-up is addressed, and the focused hook/fence tests pass (69/69). No blocking findings.

0xDEnYO
0xDEnYO previously approved these changes Nov 4, 2026
Comment thread script/utils/node-runtime-fence.test.ts Resolved
Lengthen the TMPDIR segment to 200 chars so a socket-binding launcher
fails on macOS (recent Node binds up to ~250 bytes there), and assert
the node-runtime fence's summary line so a silent exit 0 cannot pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@lifi-qa-agent lifi-qa-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 [Automated QA Review] Approved — PR #2597: no-socket regression tests now fail on macOS and Linux; reviewer nits resolved

@gvladika
gvladika merged commit 2124264 into main Nov 9, 2026
45 of 48 checks passed
@gvladika
gvladika deleted the fac/exsc-1219 branch November 9, 2026 14:01
gvladika added a commit that referenced this pull request Nov 10, 2026
…8) (#2645)

* test(fence): call withheldEnv in the no-IPC-socket fence test

#2618 renamed spawnEnv to withheldEnv while #2597 added a test calling
spawnEnv, so main fails with "spawnEnv is not defined".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(fence): build the no-IPC-socket child env with freshChildEnv

A copy of the test env carries the developer's NODE_OPTIONS into the
child, where --inspect would print to stderr and fail the assertion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(fence): build fence-test child envs with freshChildEnv

Both fence suites now spawn their direct `node --import tsx` children
from freshChildEnv, and propose-funnel-fence drops its local spawnEnv
helper. The `bun lint:funnel` spawn keeps withheldEnv because bun
reloads `.env` for unset names. The node-runtime refusal case pins
NODE_ENV=test, which it previously inherited from `bun test`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AuditNotRequired QA AI Approved Zeus QA review passed requires-types Trigger Types Bindings CI (ABI/type generation for lifi-contract-types)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants