Skip to content

Add Go M0 bash tool#49

Merged
Vasanthdev2004 merged 3 commits into
mainfrom
feat/go-m0-bash-tool
Jun 4, 2026
Merged

Add Go M0 bash tool#49
Vasanthdev2004 merged 3 commits into
mainfrom
feat/go-m0-bash-tool

Conversation

@Vasanthdev2004

@Vasanthdev2004 Vasanthdev2004 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add a Go �ash tool with shell side-effect metadata and prompt permission gating
  • run commands from a workspace-scoped cwd with configurable timeout
  • return stdout/stderr plus exit code, cwd, and timeout metadata
  • register bash through CoreTools via a shell tool group

Tests

  • added Go tests for registration, permission gating, cwd handling, outside-workspace rejection, nonzero exits, and timeout behavior
  • git diff --check
  • �un run typecheck
  • �un test ./tests --timeout 15000
  • �un run build
  • �un run smoke:build

Local blockers

  • go and gofmt are not installed on this Windows machine, so go test ./..., go build ./cmd/zero, and gofmt could not run locally
  • CI has �ctions/setup-go and will verify Go test/build/smoke on Linux, macOS, and Windows

Summary by CodeRabbit

  • New Features

    • Added a bash shell execution tool to run shell commands inside a workspace with timeout support, OS-aware shell invocation, workspace-relative cwd enforcement, and structured results (stdout/stderr, exit code, timeout metadata).
  • Tests

    • Added tests covering command execution, workspace boundary enforcement, permission/registry behavior, nonzero exit handling, and timeout scenarios.

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Zero automated PR review

Verdict: No blockers found

Blockers

  • None found.

Validation

  • [pass] Diff hygiene: git diff --check
  • [pass] Typecheck: bun run typecheck
  • [pass] Tests: bun run test
  • [pass] Build: bun run build
  • [pass] Smoke build: bun run smoke:build

Scope

Head: 94e5eafd8f31
Changed files (3): internal/tools/bash.go, internal/tools/bash_tool_test.go, internal/tools/registry.go

This deterministic review checks validation status and basic diff hygiene. A human reviewer still owns product judgment and design quality.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a19aa6d2-d3b6-435e-8bf1-04c3c746ffba

📥 Commits

Reviewing files that changed from the base of the PR and between f1805d0 and 94e5eaf.

📒 Files selected for processing (1)
  • internal/tools/bash_tool_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/tools/bash_tool_test.go

Walkthrough

Adds a workspace-scoped Bash execution tool exposing command, optional cwd and timeout_ms; runs shell commands with OS-specific shell under a timeout, captures stdout/stderr and exit codes, returns structured results with metadata, integrates the tool into core tools, and adds integration tests covering permissions, workspace boundaries, nonzero exits, and timeouts.

Changes

Bash Tool Implementation and Integration

Layer / File(s) Summary
Bash tool schema and execution
internal/tools/bash.go
NewBashTool defines the tool schema (command required, cwd and timeout_ms optional) and safety metadata. Run validates inputs, resolves workspace-relative cwd, executes the command with exec.CommandContext using an OS shell, enforces timeout, captures stdout/stderr, extracts exit codes, and returns a structured Result with formatted output and metadata (cwd, exit_code, timeout_ms).
Helper functions and utilities
internal/tools/bash.go
OS-specific shellExecutable/shellArguments, commandExitCode (maps non-exit errors to -1), and formatBashOutput that trims newlines, composes stdout/stderr, and appends exit_code when nonzero.
Registry integration
internal/tools/registry.go
Adds CoreShellTools(workspaceRoot) returning the bash tool and updates CoreTools(workspaceRoot) to append shell tools so bash is exposed among core tools.
Test infrastructure and helpers
internal/tools/bash_tool_test.go (lines 1–47, 179–190)
TestMain test harness spawns a helper subprocess to simulate deterministic behaviors (success, stderr, failure exit code, pwd, sleep). Helpers build the subprocess invocation and perform OS-appropriate quoting.
Behavior verification tests
internal/tools/bash_tool_test.go (lines 49–177)
Integration tests verify: core registry exposes bash with SideEffectShell and PermissionPrompt; registry blocks bash without grant; commands run in workspace and return stdout plus metadata (exit_code, workspace-relative cwd); explicit cwd accepted when inside workspace and rejected when outside; nonzero exits produce error status with captured stdout/stderr and exit_code; timeouts return error status with timeout message and timeout_ms metadata.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Gitlawb/zero#47: Related registry/tool-safety wiring changes that add core tool groups and registry composition.

Suggested reviewers

  • anandh8x
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Go M0 bash tool' is specific and clearly describes the main change: introducing a new bash tool implementation for the Go M0 project.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/go-m0-bash-tool

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/tools/bash_tool_test.go (1)

84-177: ⚡ Quick win

Add a registry “grant allows execution” integration test.

Current tests prove denial without grant and direct NewBashTool(...).Run(...) success, but they do not verify that Registry actually permits bash execution when the required grant is present. Adding this test closes the permission-gating contract end-to-end.

🤖 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 `@internal/tools/bash_tool_test.go` around lines 84 - 177, Add an integration
test that verifies Registry grants allow bash execution: create a test (e.g.
TestBashToolAllowsExecutionWhenRegistryGrantPresent) that constructs a Registry
instance with the specific permission/grant that enables bash execution,
register or inject that Registry so NewBashTool(...) uses it, then call
NewBashTool(root).Run(ctx, map[string]any{"command": helperCommand("success")})
and assert result.Status == StatusOK and stdout contains the expected output;
mirror existing assertions (stdout contains "hello from bash", exit_code meta
"0") to prove the grant actually permits execution.
🤖 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.

Nitpick comments:
In `@internal/tools/bash_tool_test.go`:
- Around line 84-177: Add an integration test that verifies Registry grants
allow bash execution: create a test (e.g.
TestBashToolAllowsExecutionWhenRegistryGrantPresent) that constructs a Registry
instance with the specific permission/grant that enables bash execution,
register or inject that Registry so NewBashTool(...) uses it, then call
NewBashTool(root).Run(ctx, map[string]any{"command": helperCommand("success")})
and assert result.Status == StatusOK and stdout contains the expected output;
mirror existing assertions (stdout contains "hello from bash", exit_code meta
"0") to prove the grant actually permits execution.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 336341d2-b072-48bd-817e-02ea503f6f2f

📥 Commits

Reviewing files that changed from the base of the PR and between e748206 and b555572.

📒 Files selected for processing (3)
  • internal/tools/bash.go
  • internal/tools/bash_tool_test.go
  • internal/tools/registry.go

@anandh8x anandh8x left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's good

  • Workspace boundary is enforced through resolveWorkspacePath (internal/tools/bash.go:54-56), so the same EvalSymlinks + .. rejection that protects the read/write tools applies to the shell's cwd. command.Dir = absoluteCwd therefore always points at a real, symlink-resolved directory inside the workspace.
  • Timeout is bounded both at the schema layer (Minimum: 1, Maximum: 600000) and at the runtime layer (context.WithTimeout + exec.CommandContext, internal/tools/bash.go:58-60, 62-63). When the deadline fires, errors.Is(commandCtx.Err(), context.DeadlineExceeded) is checked before the generic err != nil branch, so timeout reports don't get misclassified as a regular failure.
  • OS-aware shell invocation: shellExecutable / shellArguments (internal/tools/bash.go:107-116) return cmd.exe /d /s /c <command> on Windows and /bin/sh -c <command> elsewhere. The /d /s /c triple is the documented safe form for one-shot command execution on Windows; the set -e / set -u defaults from a login shell don't leak in.
  • Result metadata carries cwd, exit_code, and timeout_ms (internal/tools/bash.go:69-72), and formatBashOutput (internal/tools/bash.go:135-150) only emits exit_code: N for non-zero exits — keeps happy-path output clean. Stdout/stderr are trimmed of trailing newlines and only rendered if non-empty.
  • Registry wiring: CoreShellTools exposes just bash, and CoreTools appends it after read-only and write tools. Permission is SideEffectShell / PermissionPrompt, matching apply_patch / write_file / edit_file — consistent safety tier.
  • Test harness is self-contained: TestMain (internal/tools/bash_tool_test.go:13-22) detects --zero-bash-helper <name> and dispatches to a tiny in-process command library (success, stderr, fail, pwd, sleep). Avoids the cross-platform headache of depending on echo / cat / sleep having the same flags everywhere, and shellQuote handles the POSIX quoting. CI green on all four matrices; CodeRabbit reports SUCCESS.

Observations (non-blocking)

  1. Unbounded stdout/stderr buffers. var stdout bytes.Buffer; var stderr bytes.Buffer (internal/tools/bash.go:65-67) have no size cap, and the only Run completion path that returns early is the timeout. A cat /dev/urandom | head -c 10G style command (or any noisy build) can OOM the agent process before the timeout fires, because command.Run only returns after the child closes its stdout/stderr — which a 10-minute-long noisemaker could legitimately do. Wrapping the writers in io.LimitReader-style guards (e.g. io.Copy(&limitedBuffer{limit: 64 << 20}, command.Stdout)) or checking the buffer size in a WaitDelay callback would cap the worst case. Worth adding before this lands in front of users with large repos.

  2. Stdin is inherited from the parent process. command.Stdin is not set (internal/tools/bash.go:62-67). When the agent is run from a TUI / interactive terminal, the child shell will share that stdin file descriptor with the UI. A bash command that does read or cat would consume whatever the TUI was about to read, and any input the TUI was reading could be reinterpreted as command input. Setting command.Stdin = os.Stdin explicitly is also wrong here; the safe choice is either to leave it nil and document that the tool assumes a non-interactive parent or to point it at /dev/null (or os.DevNull) so the child is guaranteed EOF.

  3. Timeout exit code is -1 in metadata. When the deadline fires, command.Run() returns the kill error rather than *exec.ExitError, so commandExitCode falls through to -1 (internal/tools/bash.go:118-126). The output string is correct (timed out after Nms), but meta.exit_code = -1 is opaque. A dedicated meta["timed_out"] = "true" (and an explicit meta["exit_code"] = "-1" only if you want to keep the key stable) would let downstream consumers distinguish "command exited 7" from "killed by deadline" without string-matching the output.

  4. Env vars are inherited as-is. The child sees the full parent env, including anything the agent stashed in GITHUB_TOKEN, OPENAI_API_KEY, ANTHROPIC_API_KEY, etc. This is the standard behaviour for exec.Cmd and probably what users want, but it's worth a one-line note in the tool description (Description) so the LLM side doesn't accidentally leak secrets into a sub-process that the user can see in ps. Pairing with a --inherit-env=false opt-out (or a env: {} arg) is a reasonable follow-up.

  5. Grant-integration test is missing. CodeRabbit's only nitpick on this PR is a TestBashToolAllowsExecutionWhenRegistryGrantPresent style test that goes through Registry.RunWithOptions with PermissionGranted: true and asserts the command actually runs. TestBashToolRunsCommandInWorkspace exercises the tool directly, which proves the tool works but doesn't prove the registry's prompt path is wired up for this specific tool. The test for write_file (TestRegistryRunsPromptToolsWithGrant in #48) is a good template; copy that for bash and the gating contract is covered end-to-end.

  6. Workspace cwd uses the read-side resolver. resolveWorkspacePath requires the path to exist (EvalSymlinks will fail on ENOENT). For bash that's the right choice — a non-existent cwd would error at exec time anyway — but it means cwd: "./new-dir" errors with the EvalSymlinks lstat message rather than the friendlier "directory does not exist" text. errors.Is(err, fs.ErrNotExist) on the resolver error path would let the tool produce a more user-friendly message. Same pattern as the read_file observation in #47.

  7. Helper binary path is platform-fragile. helperCommand uses os.Args[0] to relaunch the test binary (internal/tools/bash_tool_test.go:181-184). On Linux this is fine; on Windows the test relies on the test runner using the actual .exe rather than a wrapper — and on cross-compiled runs the path could include forward slashes that cmd.exe /c doesn't love. shellQuote already special-cases Windows to skip quoting, which is the right call for now, but a runtime.GOOS == "windows" && !strings.HasSuffix(executable, ".exe") guard might be worth adding if any of the CI matrices starts being flaky.

No blockers. The output-size cap in #1 and the stdin handling in #2 are the two I'd most want to see tightened in a follow-up; the rest are correctness / documentation polish.

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.

2 participants