[BUG] Failed commands return only "exit status 1"; the command's error never reaches the agent

Offen Anfängerfreundlich
#89 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

@sathpal arbeitet bereits daran.

Seit 21.10.2026.

  • #90 von @sathpal — offen

Bewertung

Schwierigkeit
2/5
Geschätzter Aufwand
1-3 Stunden
Anfängerfreundlichkeit
69/100
Issue-Typ
Bug
Klarheit
Klar beschrieben
Aktivitätsstatus
Aktiv
Tech-Stack
go, kubernetes
Bereich
api, backend

Rechercherichtung

Beginne mit internal/commands/builder.go, insbesondere mit CommandBuilder.executeCommand und Execute; verfolge, wie internal/cmd.DefaultShellExecutor.Exec die kombinierte Ausgabe zurückgibt und wie pkg/k8s Fehler in Tool-Ergebnisse umwandelt. Prüfe die bestehenden Command-Tests und ergänze Tests für einen fehlgeschlagenen Befehl mit Ausgabe. Fertig ist die Arbeit, wenn der zurückgegebene Tool-Fehler die Befehlsausgabe enthält und die Fehlerersteller Fehler anhand dieser Meldung klassifizieren können.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

Bug · Severity: Medium · every tool that runs a CLI · no workaround on the agent side
Affects: kagent-tools v0.1.x to v0.3.0 (bundled with kagent v0.10.3) and main · Component: internal/commands (CommandBuilder.Execute)

What breaks

When a CLI command fails, the tool result tells the agent only the exit status. The command's own message, the part that says why it failed, is written to the tool server's log and dropped.

Asking kagent's k8s-agent to "Show me the YAML of the deployment does-not-exist in the default namespace" gives the agent this tool result:

[Kubernetes] get deployment does-not-exist -o yaml -n default failed: exit status 1

The tool server logs what kubectl actually said:

level=ERROR msg="command execution failed" command=kubectl args="[get deployment does-not-exist -o yaml -n default]" error="exit status 1" output="Error from server (NotFound): deployments.apps \"does-not-exist\" not found\n"

For NotFound the agent can guess. For most failures it can't. In my case an admission webhook refused a k8s_patch_resource call and said exactly what to do next:

Error from server (Forbidden): admission webhook "agents.kubeamber.dev" denied the request: kubeamber: HOLD (risk 40/100, replicas 3 → 6): risk 40/100 is at or above the hold threshold (30). Factors: +10 modifies a resource, +5 changes a workload, +25 namespace "prod" is protected. Held for human approval as change request cr-671cf823-tqrcc (expires in 1h0m0s). Ask an operator to run `kubeamber approve cr-671cf823-tqrcc`, then retry this exact change.

The agent received exit status 1. It then tried the same change three different ways (a merge patch, an annotation, and kubectl apply of an edited manifest) and asked the user whether they had RBAC permissions or "an admission controller, policy, or automation tool … restricting changes". The same happens with RBAC denials, conflicts, validation errors, and failed helm, istioctl and cilium commands.

There is a second effect. NewKubernetesError, NewHelmError and the other error creators choose suggestions and an error code by searching the cause for words such as forbidden, not found and already exists. The cause is always the exit error, so those branches never match: every kubectl failure gets K8S_GENERIC_ERROR. The Helm tools show these suggestions to the agent, so the agent gets the generic fallback for those too.

Reproduce

kagent v0.10.3 on kind with the default install. Ask k8s-agent for any resource that doesn't exist, as above, and expand the tool call's result in the UI. Then compare it with the tool server's log:

kubectl -n kagent logs deploy/kagent-tools | grep "command execution failed"

Expected vs actual

  • Expected: the tool result includes the command's output, for example ... failed: exit status 1: Error from server (NotFound): deployments.apps "does-not-exist" not found.
  • Actual: it ends at failed: exit status 1.

Ask

Keep the command's output in the error that CommandBuilder.Execute returns. I have a small PR ready that does this and adds a test.

Root cause

internal/cmd.DefaultShellExecutor.Exec runs cmd.CombinedOutput() and returns both the output and the error. CommandBuilder.executeCommand (internal/commands/builder.go; this handling dates from #7) wraps only the error:

output, err := executor.Exec(ctx, command, args...)
if err != nil {
    var toolError *errors.ToolError
    switch command {
    case "kubectl":
        toolError = errors.NewKubernetesError(strings.Join(args, " "), err)
    ...
    return string(output), toolError
}

and Execute then discards the returned output (return "", err). runKubectlCommand in pkg/k8s turns the error into the tool result with mcp.NewToolResultError(err.Error()), so only exit status 1 survives.

Vorherrschende Sprache
Go
Sterne
39
Forks
31
Ø Merge
2 T. 8 Std.
Gemergte PRs (30 T.)
2

Entwicklungsumgebung

In Codespaces öffnen

Startet den Dev-Container des Projekts im Browser, mit Ihrem eigenen GitHub-Konto.

  • Enthält ein Dockerfile oder eine Docker-Compose-Datei
  • Keine Pull-Request-Vorlage
  • Kein Beitragsleitfaden

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus kagent-dev/tools

Alle Issues in kagent-dev/tools

Ähnliche Issues

Weitere Issues zu Go

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.