vxcontrol/pentagi · error

command failed: %w: %s

Error message

command failed: %w: %s

What it means

This error wraps a non-zero exit or execution failure from a detached/background command run inside the Docker terminal sandbox. ExecCommand first runs the command with a quick check timeout; if the exec finishes with an error within that window, it combines the underlying error with whatever partial output was captured so the agent/developer sees both cause and context. It is thrown only when the exec result carries an error, e.g. the command exited non-zero or the exec could not complete.

Source

Thrown at backend/pkg/tools/terminal.go:248

		TTY:          true,
	})
	if err != nil {
		return "", fmt.Errorf("failed to create exec process: %w", err)
	}

	if detach {
		resultChan := make(chan execResult, 1)
		detachedCtx := context.WithoutCancel(ctx)

		go func() {
			output, err := t.getExecResult(detachedCtx, createResp.ID, timeout)
			resultChan <- execResult{output: output, err: err}
		}()

		select {
		case result := <-resultChan:
			if result.err != nil {
				return "", fmt.Errorf("command failed: %w: %s", result.err, result.output)
			}
			if result.output == "" {
				return "Command completed in background with exit code 0", nil
			}
			return result.output, nil
		case <-time.After(defaultQuickCheckTimeout):
			return fmt.Sprintf("Command started in background with timeout %s (still running)", timeout), nil
		}
	}

	return t.getExecResult(ctx, createResp.ID, timeout)
}

func (t *terminal) getExecResult(ctx context.Context, id string, timeout time.Duration) (string, error) {
	if timeout > 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, timeout)
		defer cancel()

View on GitHub (pinned to ea665308ba)

Solutions

  1. Read the wrapped output (%s) — it contains the command's stdout/stderr identifying the real cause
  2. Fix the underlying command (correct path, install missing tool, fix permissions)
  3. If the command legitimately returns non-zero (e.g. grep with no match), wrap it: 'cmd || true' or handle the exit code in shell
  4. If the command is interactive or long-running, re-run with detach=true so it is not judged by the quick check

Example fix

// before
result, err := term.ExecCommand(ctx, flowID, "nmap --typo-flag target", false)
// after
result, err := term.ExecCommand(ctx, flowID, "nmap -sV target || true", false)
Defensive patterns

Strategy: try-catch

Validate before calling

if strings.TrimSpace(cmd) == "" {
    return errors.New("command is empty")
}
// prefer commands that tolerate non-zero exits when "not found" is acceptable:
// strings.HasSuffix(cmd, "|| true")

Type guard

func isCmdFailedError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "command failed:")
}

Try / catch

out, err := term.ExecCommand(ctx, flowID, cmd, false)
if err != nil {
    var combined string
    if isCmdFailedError(err) {
        // err wraps exit cause + output; inspect output to decide retry vs fix
        combined = err.Error()
    }
    return fmt.Errorf("exec failed: %w (%s)", err, combined)
}

Prevention

When it happens

Trigger: Calling ExecCommand (via the terminal tool Handle) with a command that exits non-zero or fails inside the container, and the result arrives before defaultQuickCheckTimeout elapses. Also triggered when the command's exit status is surfaced as an error in execResult from the goroutine.

Common situations: Running shell commands in the pentest sandbox that reference missing binaries, bad paths, permission-denied files, or scripts that fail; typos in the command; tools that return non-zero on 'no results found'.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/1132f0015ac22b62. Report an issue: GitHub.