Treat grep no matches as nonfatal

This commit is contained in:
mirivlad 2026-05-11 00:27:44 +08:00
parent fcee065231
commit 717e931a5e
4 changed files with 31 additions and 7 deletions

View File

@ -24,10 +24,12 @@ class Tool(BaseTool):
stdin_data=str(stdin_secret) if stdin_secret is not None else None, stdin_data=str(stdin_secret) if stdin_secret is not None else None,
) )
output = completed.stdout if completed.returncode == 0 else completed.stderr or completed.stdout output = completed.stdout if completed.returncode == 0 else completed.stderr or completed.stdout
grep_no_matches = "grep" in command and completed.returncode == 1 and not completed.stderr
ok = completed.returncode == 0 or grep_no_matches
return ToolResult( return ToolResult(
tool=self.name, tool=self.name,
ok=completed.returncode == 0, ok=ok,
output=output, output=output,
error=None if completed.returncode == 0 else f"Command failed with exit code {completed.returncode}", error=None if ok else f"Command failed with exit code {completed.returncode}",
metadata={"exit_code": completed.returncode}, metadata={"exit_code": completed.returncode, "no_matches": grep_no_matches},
) )

View File

@ -29,6 +29,8 @@ class ShellExecTool(BaseTool):
) )
output = completed.stdout if completed.returncode == 0 else completed.stderr or completed.stdout output = completed.stdout if completed.returncode == 0 else completed.stderr or completed.stdout
error_output = completed.stderr or completed.stdout error_output = completed.stderr or completed.stdout
grep_no_matches = "grep" in command and completed.returncode == 1 and not completed.stderr
ok = completed.returncode == 0 or grep_no_matches
is_sudo_error = ( is_sudo_error = (
completed.returncode != 0 and completed.returncode != 0 and
@ -40,8 +42,8 @@ class ShellExecTool(BaseTool):
return ToolResult( return ToolResult(
tool=self.name, tool=self.name,
ok=completed.returncode == 0, ok=ok,
output=output, output=output,
error=None if completed.returncode == 0 else f"Command failed with exit code {completed.returncode}", error=None if ok else f"Command failed with exit code {completed.returncode}",
metadata={"exit_code": completed.returncode, "needs_sudo": is_sudo_error}, metadata={"exit_code": completed.returncode, "needs_sudo": is_sudo_error, "no_matches": grep_no_matches},
) )

View File

@ -14,6 +14,9 @@ INSTRUCTIONS:
4. If the user asks about the current local machine, filesystem, processes, 4. If the user asks about the current local machine, filesystem, processes,
packages, logs, runtime state, or anything that must be observed rather than packages, logs, runtime state, or anything that must be observed rather than
answered from general knowledge, use an appropriate tool. answered from general knowledge, use an appropriate tool.
5. For exploratory tasks, prefer one robust inspection command over many brittle
dependent checks. Missing optional files should be treated as information, not
as a fatal failure.
MODE: {mode_hint} MODE: {mode_hint}
- If mode is "execution": create a plan with TOOL STEPS (shell_exec, file_write, etc) - If mode is "execution": create a plan with TOOL STEPS (shell_exec, file_write, etc)

View File

@ -112,6 +112,23 @@ def test_shell_exec_allows_safe_command(tmp_path: Path) -> None:
assert str(tmp_path) in result["result"]["output"] assert str(tmp_path) in result["result"]["output"]
def test_shell_exec_treats_grep_no_matches_as_information(tmp_path: Path) -> None:
_write_config_tree(tmp_path)
controller = RuntimeController(base_dir=tmp_path)
result = controller.handle_task(
UserTask(
input="run grep with no matches",
context={
"requested_tool": "shell_exec",
"tool_args": {"command": "printf 'abc\\n' | grep definitely_missing"},
},
)
)
assert result["status"] == "completed"
assert result["result"]["metadata"]["exit_code"] == 1
assert result["result"]["metadata"]["no_matches"] is True
def test_permission_resolution_can_resume_task(tmp_path: Path) -> None: def test_permission_resolution_can_resume_task(tmp_path: Path) -> None:
_write_config_tree(tmp_path) _write_config_tree(tmp_path)
controller = RuntimeController(base_dir=tmp_path) controller = RuntimeController(base_dir=tmp_path)