MCPcopy Index your code
hub / github.com/codehamr/codehamr / Bash

Function Bash

internal/tools/bash.go:38–96  ·  view source on GitHub ↗

Bash runs one shell command through /bin/sh -c and returns combined stdout+stderr. Non-zero exit is not an error; the model sees the failure and reacts. A pre-cancelled parent (Ctrl+C raced the dispatch) returns "(cancelled)" before the blank-command check, so a trivially-cancelled call isn't mista

(parent context.Context, command string, timeout time.Duration)

Source from the content-addressed store, hash-verified

36// before the blank-command check, so a trivially-cancelled call isn't
37// mistaken for a valid empty-args invocation.
38func Bash(parent context.Context, command string, timeout time.Duration) string {
39 if parent.Err() != nil {
40 return "(cancelled)"
41 }
42 if strings.TrimSpace(command) == "" {
43 return "(empty command)"
44 }
45 ctxT, cancel := context.WithTimeout(parent, timeout)
46 defer cancel()
47
48 cmd := exec.CommandContext(ctxT, "/bin/sh", "-c", command)
49 // Shell gets its own process group + a Cancel that kills the whole group
50 // on cancel/timeout (Unix; no-op on Windows). Without it, backgrounded
51 // children (`cmd &`) outlive the parent shell and leak.
52 setProcessGroup(cmd)
53 // Cap the wait for stdout/stderr pipes to close after /bin/sh exits.
54 // Backgrounded children inherit those pipe fds, so without this Run's
55 // pipe-copy goroutine blocks for the full timeout even though the shell
56 // is gone.
57 cmd.WaitDelay = 100 * time.Millisecond
58 // Bounded combined output instead of CombinedOutput's unbounded buffer: a
59 // high-throughput command (`cat big.iso`, `grep -r "" /`) can emit hundreds
60 // of MB/s and OOM-kill the whole TUI well before the timeout or Ctrl+C
61 // react. ctx.Truncate keeps only head+tail anyway, so nothing the model
62 // would see is lost. Stdout and Stderr get the SAME writer value, which
63 // os/exec detects and funnels through one pipe: no locking needed.
64 buf := &headTailBuffer{}
65 cmd.Stdout = buf
66 cmd.Stderr = buf
67 err := cmd.Run()
68 s := buf.String()
69 // Name the capture drop at the END of the output, where ctx.Truncate's
70 // tail keep guarantees the model sees it: the in-band seam marker sits at
71 // the ~1MB offset, always inside Truncate's dropped middle, and Truncate's
72 // own "total" would count the collapsed string, under-reporting the real
73 // size by orders of magnitude.
74 if d := buf.droppedBytes(); d > 0 {
75 s += fmt.Sprintf("\n(output capped at capture: %d bytes total, %d bytes dropped mid-stream)", buf.totalBytes(), d)
76 }
77 if err != nil {
78 switch {
79 case ctxT.Err() == context.DeadlineExceeded:
80 return s + fmt.Sprintf("\n(timeout after %s)", timeout)
81 case parent.Err() == context.Canceled || ctxT.Err() == context.Canceled:
82 // User Ctrl+C; name it rather than leak "signal: killed" noise.
83 return s + "\n(cancelled)"
84 case errors.Is(err, exec.ErrWaitDelay):
85 // Shell exited 0; err is non-nil only because a backgrounded child
86 // held the pipes past WaitDelay, not a failure. Return output as-is
87 // so it isn't mislabeled with a spurious (exit: ...). After the
88 // cancel/timeout cases so those signals win over a coincident delay.
89 return s
90 default:
91 // Exit errors go into the output, exactly what the model needs.
92 s += fmt.Sprintf("\n(exit: %v)", err)
93 }
94 }
95 return s

Calls 4

StringMethod · 0.95
droppedBytesMethod · 0.95
totalBytesMethod · 0.95
setProcessGroupFunction · 0.70