Environment
- qwen-code version: 0.21.13
Problem
When a shell command embeds multi-line Python via heredoc (or any newline outside quotes),
the permission system splits the command per line and evaluates each line as an independent
command. Each line's first word (e.g. import, print) is treated as a command name;
per-line prompts appear and allow rules like Bash(python *) never match the whole command,
so it can never be auto-approved.
Reproduction
-
Configure permissions.allow: ["Bash(python *)"].
-
Run in ask/auto mode:
python - <<'PY'
import os
print(os.getcwd())
PY
-
Observed: the command is split into ["python - <<'PY'", "import os", "print(os.getcwd())", "PY"];
segments 2-3 match no allow rule and fall through to per-line ask/classifier evaluation.
Minimal repro of the splitter (logic from chunk-4S5337WV.js):
const SHELL_OPERATORS = ["&&", "||", ";;", "|&", "|", ";", "&", "\n"];
// splitCompoundCommandSegments("python - <<'PY'\nimport os\nprint(1)\nPY")
// -> ["python - <<'PY'", "import os", "print(1)", "PY"]
Root cause
In chunk-4S5337WV.js:
splitCompoundCommandSegments treats \n as a shell operator (SHELL_OPERATORS, ~line 43255).
- The rule-evaluation path
splitCompoundCommand() (~line 43342) splits the raw command
WITHOUT stripping heredoc bodies.
- The file-operation path
walkCompoundCommand() (~line 47677) calls
splitCompoundCommandSegments(stripHeredocBodies(command)) — heredoc bodies ARE stripped there.
The two paths are inconsistent: heredoc bodies are only stripped in the file-op path, not in
the permission rule-evaluation path.
Expected
python - <<'PY' ... PY (and any command whose newlines live inside quotes or a heredoc body)
is treated as a single command, so Bash(python *) allow rules match it.
Suggested fix
Strip heredoc bodies inside splitCompoundCommand(), mirroring walkCompoundCommand().
Environment
Problem
When a shell command embeds multi-line Python via heredoc (or any newline outside quotes),
the permission system splits the command per line and evaluates each line as an independent
command. Each line's first word (e.g.
import,print) is treated as a command name;per-line prompts appear and allow rules like
Bash(python *)never match the whole command,so it can never be auto-approved.
Reproduction
Configure
permissions.allow: ["Bash(python *)"].Run in ask/auto mode:
Observed: the command is split into
["python - <<'PY'", "import os", "print(os.getcwd())", "PY"];segments 2-3 match no allow rule and fall through to per-line ask/classifier evaluation.
Minimal repro of the splitter (logic from
chunk-4S5337WV.js):Root cause
In
chunk-4S5337WV.js:splitCompoundCommandSegmentstreats\nas a shell operator (SHELL_OPERATORS, ~line 43255).splitCompoundCommand()(~line 43342) splits the raw commandWITHOUT stripping heredoc bodies.
walkCompoundCommand()(~line 47677) callssplitCompoundCommandSegments(stripHeredocBodies(command))— heredoc bodies ARE stripped there.The two paths are inconsistent: heredoc bodies are only stripped in the file-op path, not in
the permission rule-evaluation path.
Expected
python - <<'PY' ... PY(and any command whose newlines live inside quotes or a heredoc body)is treated as a single command, so
Bash(python *)allow rules match it.Suggested fix
Strip heredoc bodies inside
splitCompoundCommand(), mirroringwalkCompoundCommand().