Skip to content

fix: 避免 && 短路时误用未执行命令的退出码语义 - #1347

Open
ai-yang wants to merge 1 commit into
claude-code-best:mainfrom
ai-yang:audit/fix-bash-command-semantics
Open

fix: 避免 && 短路时误用未执行命令的退出码语义#1347
ai-yang wants to merge 1 commit into
claude-code-best:mainfrom
ai-yang:audit/fix-bash-command-semantics

Conversation

@ai-yang

@ai-yang ai-yang commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • 使用保留控制运算符的命令解析结果识别 && list。
  • 当聚合退出码无法确定来自哪个语法段时,回退到默认非零退出码语义。
  • 保留简单 rg、管道和纯 || 的既有特殊语义。
  • 增加前置失败 + rg / diff 短路回归及 false || rg 控制组。

这样可以避免 test-command && rg ... 中测试失败时,把根本未执行的 rg 当成“无匹配”。

Test plan

  • bun test packages/builtin-tools/src/tools/BashTool/__tests__/commandSemantics.test.ts(14 pass)
  • bun run typecheck
  • Biome check(改动文件)
  • git diff --check

关联 issue

Closes #1344

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of compound shell commands using && and ||.
    • Commands that are skipped due to short-circuiting no longer incorrectly affect the reported result.
    • Preserved accurate fallback behavior for commands reached through ||.
    • Existing pipeline command handling remains unchanged.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The BashTool now uses conservative default exit-code semantics for ambiguous && command lists. Tests cover skipped rg and diff commands and preserve special rg semantics when reached through ||.

Changes

Bash command semantics

Layer / File(s) Summary
Handle ambiguous && command lists
packages/builtin-tools/src/tools/BashTool/commandSemantics.ts
The command extraction logic detects && lists and avoids selecting a possibly unexecuted final command. Pipeline handling remains unchanged.
Cover short-circuit semantics
packages/builtin-tools/src/tools/BashTool/__tests__/commandSemantics.test.ts
Tests verify that skipped rg and diff commands do not change error interpretation. A reached rg command through `

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

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了修复 && 短路时误用未执行命令退出码语义这一主要变更。
Linked Issues check ✅ Passed 实现识别 && 并在语义不确定时回退默认错误语义,同时保留既有 rg、管道和纯 || 语义,符合 Issue #1344
Out of Scope Changes check ✅ Passed 变更仅涉及命令语义解析和对应回归测试,均与 Issue #1344 的修复目标直接相关。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Comment @coderabbitai help to get the list of available commands.

@ai-yang
ai-yang marked this pull request as ready for review August 11, 2026 11:47

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/builtin-tools/src/tools/BashTool/commandSemantics.ts`:
- Around line 123-125: Update command-semantics selection around
splitCommandWithOperators and splitCommand_DEPRECATED so parse-fallback results
are detected before choosing a command. Propagate parse status or an equivalent
quote-aware ambiguity signal, ensuring fallback commands containing operators
such as && are not classified by their first token; preserve correct exit-code
failure handling and add a regression test for the rg ${var + expr} && echo
case.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ce43b9a4-6384-459b-adc2-0ed4a79dadb3

📥 Commits

Reviewing files that changed from the base of the PR and between 3bb6b57 and 9be2d21.

📒 Files selected for processing (2)
  • packages/builtin-tools/src/tools/BashTool/__tests__/commandSemantics.test.ts
  • packages/builtin-tools/src/tools/BashTool/commandSemantics.ts

Comment on lines +123 to +125
if (splitCommandWithOperators(command).includes('&&')) {
return ''
}

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -A25 -B8 \
  'parseResult\.success|return \[commandOriginalJoined\]' \
  src/utils/bash/commands.ts

rg -n -A30 -B8 \
  'heuristicallyExtractBaseCommand|splitCommand_DEPRECATED' \
  packages/builtin-tools/src/tools/BashTool/commandSemantics.ts

Repository: claude-code-best/claude-code

Length of output: 11736


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- command splitter implementation ---'
sed -n '80,255p' src/utils/bash/commands.ts

printf '%s\n' '--- semantic tests and call sites ---'
rg -n -C 8 \
  'interpretCommandResult|heuristicallyExtractBaseCommand|splitCommandWithOperators|splitCommand_DEPRECATED' \
  packages/builtin-tools/src src --glob '*.{ts,tsx}' \
  | head -n 500

printf '%s\n' '--- parser dependency and relevant test files ---'
rg -n -C 5 \
  'splitCommandWithOperators|parse failed|malformed|Bad substitution|&&' \
  src packages/builtin-tools --glob '*.{test,spec}.{ts,tsx}' --glob '*.ts' \
  | head -n 500

Repository: claude-code-best/claude-code

Length of output: 50384


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- legacy splitter ---'
sed -n '251,365p' src/utils/bash/commands.ts

printf '%s\n' '--- command semantics files and tests ---'
fd -i 'commandSemantics' packages/builtin-tools/src
fd -i 'test' packages/builtin-tools/src/tools/BashTool | rg 'Semantics|command|BashTool'

printf '%s\n' '--- direct semantic references ---'
rg -n -C 12 \
  'interpretCommandResult|COMMAND_SEMANTICS|splitCommandWithOperators' \
  packages/builtin-tools/src/tools/BashTool/commandSemantics.ts \
  packages/builtin-tools/src/tools/BashTool --glob '*.{test,spec}.{ts,tsx}'

printf '%s\n' '--- shell parser implementation ---'
fd -i 'shellQuote' src
rg -n -C 12 \
  'function tryParseShellCommand|export function tryParseShellCommand|Bad substitution' \
  src/utils/bash

Repository: claude-code-best/claude-code

Length of output: 31607


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- remainder of legacy splitter ---'
sed -n '340,410p' src/utils/bash/commands.ts

printf '%s\n' '--- complete Bash command semantics tests ---'
cat -n packages/builtin-tools/src/tools/BashTool/__tests__/commandSemantics.test.ts

printf '%s\n' '--- package metadata for shell-quote ---'
rg -n -C 3 '"shell-quote"|packageManager|bun' package.json bun.lock package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null | head -n 120 || true

printf '%s\n' '--- parser result type and error handling ---'
sed -n '1,75p' src/utils/bash/shellQuote.ts

Repository: claude-code-best/claude-code

Length of output: 17098


🌐 Web query:

shell-quote 1.8.3 parse "Bad substitution" ${var + expr}

💡 Result:

The "Bad substitution" error in the shell-quote library—including version 1.8.3 and others—occurs because the library's internal tokenizer is not a full-featured Bash interpreter [1][2][3]. The library only supports simple parameter expansion in the format of $VARNAME or ${VARNAME} [2][4][5]. It does not support complex Bash features such as: * Arithmetic expansion (e.g., $((...))) [1][5] * Command substitution (e.g., $(...)) [1][5] * Parameter expansion with modifiers (e.g., ${var:-default}, ${var/a/b}) [1][5] * Array indexing (e.g., ${arr[i]}) [1][6] The specific error "Bad substitution" is triggered when the parser encounters whitespace inside a ${...} block or other complex syntax it cannot resolve [1][2][5]. Because the library uses a simplified, regex-based tokenizer, expressions like ${var + expr} contain characters (such as spaces or operators) that the parser does not recognize as valid for a simple variable name, leading it to throw an error rather than ignoring or evaluating the expression [1][2][7]. If your application requires evaluation of complex Bash syntax, the recommended approach is to avoid using shell-quote for parsing such commands and instead execute the commands directly via a shell (e.g., using Node.js child_process.execFile or spawn) [1]. If you must use the library for validation or sanitization, common workarounds in existing projects include catching the "Bad substitution" error and treating the entire command string as an opaque block, or skipping the parser for complex commands [8][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

commands = Path("src/utils/bash/commands.ts").read_text()
semantics = Path(
    "packages/builtin-tools/src/tools/BashTool/commandSemantics.ts"
).read_text()

# Model only the documented parse-failure branches and the exact operations
# shown in the reviewed source. This does not execute repository code.
parse_failed = "rg ${var + expr} && echo"
fallback = [parse_failed]  # splitCommandWithOperators on !parseResult.success
operator_present = "&&" in fallback
legacy_segments = [part for part in fallback if part not in {
    "&&", "||", ";", "|", "&", ">", ">>", ">&"
}]
base_command = legacy_segments[-1].strip().split()[0]
semantic = "rg" if base_command == "rg" else "default"

print({
    "source_has_parse_failure_fallback": "return [commandOriginalJoined]" in commands,
    "source_checks_exact_operator_token": ".includes('&&')" in semantics,
    "fallback_result": fallback,
    "exact_operator_detected": operator_present,
    "legacy_segments": legacy_segments,
    "base_command": base_command,
    "selected_semantic": semantic,
    "exit_1_is_error": semantic == "default",
})
PY

printf '%s\n' '--- splitter tests for quoted and escaped operators ---'
rg -n -C 8 \
  'splitCommandWithOperators|escaped.*&&|quoted.*&&|&&.*quoted|&&.*escaped' \
  src packages --glob '*.{test,spec}.{ts,tsx}' | head -n 300

Repository: claude-code-best/claude-code

Length of output: 6641


Handle parse-fallback commands before selecting command semantics.

On parse failure, splitCommandWithOperators returns the complete command as one element. For rg ${var + expr} && echo, the && check misses the operator, and splitCommand_DEPRECATED selects rg. Exit code 1 is then treated as “No matches found” instead of a command failure. Propagate parse status or a quote-aware ambiguity signal, and add a regression test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/builtin-tools/src/tools/BashTool/commandSemantics.ts` around lines
123 - 125, Update command-semantics selection around splitCommandWithOperators
and splitCommand_DEPRECATED so parse-fallback results are detected before
choosing a command. Propagate parse status or an equivalent quote-aware
ambiguity signal, ensuring fallback commands containing operators such as && are
not classified by their first token; preserve correct exit-code failure handling
and add a regression test for the rg ${var + expr} && echo case.

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.

1 participant