From af8caa6ea4cd4d20286db23ec0ca44b63dedcdf3 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 11 May 2026 19:27:46 +0900 Subject: [PATCH] feat(acp): surface session/update tool_call notifications at Info level MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit session/update notifications carrying a ToolCall (or inline tool_call / tool_call_update kind) were only dumped at Debug level inside the params blob. Operators could see `security.tool_granted` (permission granted) but had no way to tell whether the tool actually executed successfully — both "permission granted then failed silently" and "permission granted and succeeded" looked identical in journalctl. Add a structured Info log emitting toolCallId, name/title, status, and a content preview (truncated at 400 chars) whenever the notification contains tool-call state. This is what made it possible to diagnose the recent .goclaw/-path-deny regression — `status=failed` immediately after `security.tool_granted` revealed the gap that the granted-only log hid. No behavior change beyond logging volume; preview is bounded. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/providers/acp/process.go | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/internal/providers/acp/process.go b/internal/providers/acp/process.go index 7e34de515..9a404eed3 100644 --- a/internal/providers/acp/process.go +++ b/internal/providers/acp/process.go @@ -216,6 +216,38 @@ func (pp *ProcessPool) spawn(ctx context.Context, poolKey string) (*ACPProcess, slog.Warn("acp: session/update parse failed", "error", err) return } + // Surface tool-call notifications at Info level so operators can + // distinguish "permission granted" (security.tool_granted) from + // "tool actually executed / produced output". Without this the + // status transitions are buried in Debug-level params dumps. + if update.ToolCall != nil { + preview := "" + for _, c := range update.ToolCall.Content { + if c.Type == "text" && c.Text != "" { + preview += c.Text + if len(preview) > 400 { + break + } + } + } + if len(preview) > 400 { + preview = preview[:400] + "...(truncated)" + } + slog.Info("acp: tool_call_update", + "sid", update.SessionID, + "id", update.ToolCall.ID, + "name", update.ToolCall.Name, + "status", update.ToolCall.Status, + "content_preview", preview) + } + if update.Update.SessionUpdate == "tool_call" || update.Update.SessionUpdate == "tool_call_update" { + slog.Info("acp: tool_call_update (inline)", + "sid", update.SessionID, + "id", update.Update.ToolCallID, + "title", update.Update.Title, + "kind", update.Update.Kind, + "status", update.Update.Status) + } proc.dispatchUpdate(update) } }