Skip to content

Commit a2e2ca3

Browse files
committed
feat(ai-chat): auto-allow read-only bash in Edit Mode
Mirrors the Claude Code CLI's default permissions.allow set (git status / log / diff / show / remote show / branch / ls-files / rev-parse, plus generic ls / pwd / cat / head / tail / wc / which / file / stat / echo, and version probes) so the user isn't prompted for every "look around the repo" command. Anything containing shell composition characters (; && || | $(...) backticks < >) still falls through to a user prompt, so chained destructive operations can't piggy-back on a safe prefix.
1 parent 13574d7 commit a2e2ca3

1 file changed

Lines changed: 60 additions & 0 deletions

File tree

src-node/claude-code-agent.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,53 @@ function _isToolResponseError(toolResponse) {
114114
return false;
115115
}
116116

117+
// Bash commands the agent can run without prompting the user in Edit
118+
// Mode. Mirrors the CLI's default "permissions.allow" set
119+
// (cli.js:2925) plus a small handful of universally read-only shell
120+
// utilities. Shell-composition characters (`;`, `&&`, `||`, backticks,
121+
// pipes, redirection, command substitution) trip the safety belt
122+
// below — without that check `git status; rm -rf /` would slip through
123+
// since the prefix matches.
124+
const _SAFE_BASH_PATTERNS = [
125+
// git read-only
126+
/^git\s+status(\s|$)/,
127+
/^git\s+log(\s|$)/,
128+
/^git\s+diff(\s|$)/,
129+
/^git\s+show(\s|$)/,
130+
/^git\s+branch(\s|$)/,
131+
/^git\s+ls-files(\s|$)/,
132+
/^git\s+rev-parse(\s|$)/,
133+
/^git\s+remote\s+show(\s|$)/,
134+
/^git\s+--version$/,
135+
// generic read-only shell
136+
/^ls(\s|$)/,
137+
/^pwd$/,
138+
/^echo(\s|$)/,
139+
/^which\s/,
140+
/^cat(\s|$)/,
141+
/^head(\s|$)/,
142+
/^tail(\s|$)/,
143+
/^wc(\s|$)/,
144+
/^file\s/,
145+
/^stat\s/,
146+
// version probes
147+
/^node\s+--version$/,
148+
/^npm\s+--version$/,
149+
/^yarn\s+--version$/,
150+
/^pnpm\s+--version$/
151+
];
152+
153+
function _isSafeReadOnlyBash(rawCmd) {
154+
const cmd = (rawCmd || "").trim();
155+
if (!cmd) { return false; }
156+
// Reject anything that could chain a destructive op via shell
157+
// composition: `;` `&&` `||` `|` backticks `$(...)` `<` `>`.
158+
// The CLI's parser handles these; we keep matching simple by
159+
// refusing to bypass the prompt if any of them are present.
160+
if (/[;&|`$()<>]/.test(cmd)) { return false; }
161+
return _SAFE_BASH_PATTERNS.some(function (rx) { return rx.test(cmd); });
162+
}
163+
117164
/**
118165
* Lazily import the ESM @anthropic-ai/claude-code module.
119166
*/
@@ -916,6 +963,19 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale,
916963
}
917964
// Edit Mode: ask user confirmation before running bash
918965
const command = input.tool_input.command || "";
966+
// Skip prompting for well-known read-only commands
967+
// that mirror the Claude Code CLI's default safe
968+
// patterns. Cuts down on prompt fatigue during
969+
// typical "look around the repo" turns.
970+
if (_isSafeReadOnlyBash(command)) {
971+
console.log("[Phoenix AI] Auto-allowing safe bash:", command.slice(0, 80));
972+
return {
973+
hookSpecificOutput: {
974+
hookEventName: "PreToolUse",
975+
permissionDecision: "allow"
976+
}
977+
};
978+
}
919979
console.log("[Phoenix AI] Bash confirmation requested:", command.slice(0, 80));
920980
nodeConnector.triggerPeer("aiBashConfirm", {
921981
requestId: requestId,

0 commit comments

Comments
 (0)