From e2eaf4ac2fc3a2bf625d893dcf857a153af0936a Mon Sep 17 00:00:00 2001 From: Jamkris Date: Thu, 14 May 2026 12:23:55 +0900 Subject: [PATCH] fix(hooks): cover brace groups + yarn-run/bun-bare dev variants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two false-negatives surfaced in PR #1889 review: 1. Brace-group bypass (Greptile). `{ npm run dev; }` evaluates the dev command in the *current* shell — semantically distinct from `( ... )` but with the same effect for this hook. `splitShellSegments` correctly cleaves the group at `;` into `["{ npm run dev", "}"]`, but the first segment's leading token under `readToken` is the bare `{`, which was not in `DEV_COMMAND_WORDS`, so the dev-pattern check was skipped. Fix: treat `{` and `}` as no-op tokens in `getLeadingCommandWord` so we keep walking to the real command word. Matches how shell itself parses brace groups (the braces are reserved words, not commands). Bash requires a space after `{` and a terminator before `}` for an actual group, so `{npm run dev}` correctly remains allowed (single token `{npm`, not in `DEV_COMMAND_WORDS`). 2. Missing yarn-run / bun-bare variants (CodeRabbit). Both `yarn dev` *and* `yarn run dev` are valid (the latter is what `package.json` actually wires `dev` to under yarn 1.x). The same `(run )?` symmetry applies to bun. The previous `DEV_PATTERN` only matched `yarn\s+dev` and `bun\s+run\s+dev`, allowing the cross forms to pass through silently. Fix: `yarn(?:\s+run)?\s+dev` and `bun(?:\s+run)?\s+dev` — same shape `pnpm(?:\s+run)?\s+dev` was already using. Verified after this commit (every form now exits 2): { npm run dev; } { npm run dev ; } echo hi && { npm run dev; } ({ npm run dev; }) $( { npm run dev; } ) yarn run dev bun dev Verified still allowed (no regression): echo "{ npm run dev; }" # literal inside double quotes {npm run dev} # not a brace group per bash syntax --- scripts/hooks/pre-bash-dev-server-block.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/hooks/pre-bash-dev-server-block.js b/scripts/hooks/pre-bash-dev-server-block.js index d3eea48b..cd663fa7 100755 --- a/scripts/hooks/pre-bash-dev-server-block.js +++ b/scripts/hooks/pre-bash-dev-server-block.js @@ -127,6 +127,8 @@ function getLeadingCommandWord(segment) { continue; } + if (token === '{' || token === '}') continue; + if (/^[A-Za-z_][A-Za-z0-9_]*=.*/.test(token)) continue; const normalizedToken = normalizeCommandWord(token); @@ -159,7 +161,7 @@ process.stdin.on('data', chunk => { }); const TMUX_LAUNCHER = /^\s*tmux\s+(new|new-session|new-window|split-window)\b/; -const DEV_PATTERN = /\b(npm\s+run\s+dev|pnpm(?:\s+run)?\s+dev|yarn\s+dev|bun\s+run\s+dev)\b/; +const DEV_PATTERN = /\b(npm\s+run\s+dev|pnpm(?:\s+run)?\s+dev|yarn(?:\s+run)?\s+dev|bun(?:\s+run)?\s+dev)\b/; /** * Collect every command-line segment we should evaluate. Returns the top-level