Skip to content

feat(runtime): render agent event stream live during execution#3186

Open
ralphbean wants to merge 15 commits into
mainfrom
feat/agent-event-stream-rendering
Open

feat(runtime): render agent event stream live during execution#3186
ralphbean wants to merge 15 commits into
mainfrom
feat/agent-event-stream-rendering

Conversation

@ralphbean

@ralphbean ralphbean commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

Renders the agent's event stream live during fullsend run, replacing the old progress output that only showed periodic heartbeats and tool names.

  • Define normalized AgentEvent types (InitEvent, ThinkingEvent, TextEvent, ToolUseEvent, TokensEvent, ResultEvent, ErrorEvent, RetryEvent) that bridge runtime-specific NDJSON parsing and runtime-agnostic rendering
  • Add OnEvent callback to RunParams for custom event handling
  • Add EventRenderer that renders structured terminal output: thinking (🧠 italic/muted), text (💬), tool calls (⚙ blue), heartbeats (⏳ muted), token usage, errors, retries, and result summaries
  • Refactor Claude Code NDJSON parser to emit normalized events via callback, with assistant message fallback for non-streaming output
  • Wire it all up in ClaudeRuntime.Run
→ Agent: claude-opus-4-6 (v2.1.91)
  🧠 Let me start by reading the AGENTS.md file...
  💬 I'll read the project instructions and fetch the issue details.
  ⚙ Read: /sandbox/workspace/fullsend-2/AGENTS.md
  ⚙ Bash: gh pr list --repo fullsend-ai/fullsend --state open
  ⚙ Skill: issue-labels
  ⏳ Agent running (30s elapsed, 9m30s remaining)
→ Result: success
    Turns: 15
    Cost: $0.3592
    Tokens: in=12 out=4110 cache_create=28320 cache_read=158872

Closes #3170.

Test plan

🤖 Generated with Claude Code

@ralphbean ralphbean requested a review from a team as a code owner July 6, 2026 21:54
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 6, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 9:55 PM UTC · Ended 9:56 PM UTC
Commit: e8381e3 · View workflow run →

@ralphbean ralphbean changed the title feat(runtime): improve agent event stream rendering feat(runtime): render agent event stream live during execution Jul 6, 2026
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Site preview

Preview: https://c25e80c4-site.fullsend-ai.workers.dev

Commit: 9d6ee4e70b30d488defd03254f491dbac5fbbfb3

@ralphbean

Copy link
Copy Markdown
Member Author

/fs-review

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

feat(runtime): improve agent event stream rendering

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Introduce normalized AgentEvent interface and 8 concrete event types (InitEvent,
 ThinkingEvent, TextEvent, ToolUseEvent, TokensEvent, ResultEvent, ErrorEvent,
 RetryEvent) as a runtime-agnostic bridge between Claude's NDJSON parser and terminal rendering.
• Replace progressParser with parseClaudeStream that processes full stream-json deltas
 (thinking, text, tool input JSON, token usage, errors, retries) and emits events via a callback;
 keep progressParser as a thin backward-compatible wrapper.
• Add EventRenderer that consumes AgentEvent values and renders structured colored output:
 suppresses duplicate init headers, renders thinking (🧠) and text (💬) content, differentiates tool
 calls (⚙ blue) from heartbeat timer (⏳ muted), and shows full Bash commands truncated at 120 chars.
• Add OnEvent func(AgentEvent) to RunParams so callers can inject custom event handlers; wire
 default EventRenderer in ClaudeRuntime.Run when OnEvent is nil.
• Remove allowedTools blocklist — all tool names are now shown; extractSafeContext returns empty
 for unrecognized tools, and add context extraction for Agent (prompt) tool.
• Add ToolProgress printer method with blue ⚙ icon; change Heartbeat icon from ⟳ to ⏳ for timer
 events.
• Add comprehensive tests: renderer unit tests, parseClaudeStream tests for all event types,
 duplicate-init suppression, assistant fallback text/thinking, token throttling, and
 stream-vs-fallback deduplication.
Diagram

graph TD
    A["ClaudeRuntime.Run"] --> B["parseClaudeStream"]
    B --> C{"onEvent callback"}
    C -->|"OnEvent set"| D["Custom Handler"]
    C -->|"OnEvent nil"| E["EventRenderer"]
    E --> F["ui.Printer"]
    B --> G["AgentEvent types"]
    G --> H["InitEvent / ThinkingEvent / TextEvent"]
    G --> I["ToolUseEvent / TokensEvent"]
    G --> J["ResultEvent / ErrorEvent / RetryEvent"]
    E --> K["RunMetrics"]
    F --> L["Terminal / GHA log"]

    subgraph Legend
      direction LR
      _proc["Process"] ~~~ _data["Data"] ~~~ _out[("Output")]
    end
Loading
High-Level Assessment

The three-layer approach (normalized event types → parser callback → renderer) is the right design for this problem. It cleanly separates Claude-specific NDJSON parsing from runtime-agnostic rendering, keeps the callback synchronous (no channels/goroutines needed), and leaves a clear extension point for future runtimes (opencode) to plug in their own parser. Alternatives considered: (1) channels instead of callbacks — adds goroutine complexity with no benefit since the parser already blocks on I/O; (2) embedding rendering directly in the parser — would couple Claude-specific logic to terminal output and make testing harder; (3) a shared transcript-based approach — deferred rendering loses the live streaming benefit. The PR's approach is optimal.

Files changed (11) +2895 / -163

Enhancement (3) +23 / -3
claude.goWire OnEvent callback and default EventRenderer in ClaudeRuntime.Run +12/-1

Wire OnEvent callback and default EventRenderer in ClaudeRuntime.Run

• Replaces the 'progressParser' call with 'parseClaudeStream(r, handler)'. When 'params.OnEvent' is nil, creates a default 'EventRenderer' and wraps it to also populate 'metrics.Model' from 'InitEvent'. When 'OnEvent' is set, delegates directly to the caller's handler.

internal/runtime/claude.go

runtime.goAdd OnEvent func(AgentEvent) field to RunParams +2/-1

Add OnEvent func(AgentEvent) field to RunParams

• Adds an optional 'OnEvent func(AgentEvent)' field to 'RunParams'. When non-nil, the runtime calls it with normalized events during execution; when nil, the default 'EventRenderer' is used.

internal/runtime/runtime.go

ui.goAdd ToolProgress printer method; change Heartbeat icon to ⏳ +9/-1

Add ToolProgress printer method; change Heartbeat icon to ⏳

• Adds 'ToolProgress(text string)' that renders with blue 'ColorInfo' and a ⚙ icon for tool invocations. Changes 'Heartbeat' icon from ⟳ to ⏳ to visually distinguish timer/waiting events from active tool calls.

internal/ui/ui.go

Refactor (1) +264 / -109
claude_progress.goRefactor: replace progressParser with parseClaudeStream emitting normalized AgentEvents +264/-109

Refactor: replace progressParser with parseClaudeStream emitting normalized AgentEvents

• Rewrites the NDJSON parser as 'parseClaudeStream(r, onEvent)' that processes all stream-json event types: system init/retry, stream_event deltas (text, thinking, tool input JSON accumulation, token usage), result, error, and assistant fallback. Removes 'allowedTools' blocklist and 'emitToolProgress'/'parseAssistantToolUse' helpers. Replaces 'extractBinaryName' with 'collapseCommand' that shows full commands truncated at 120 chars. Adds 'Agent' prompt context extraction. Keeps 'progressParser' as a thin wrapper.

internal/runtime/claude_progress.go

Tests (3) +573 / -51
event_test.goNew: compile-time interface satisfaction test for all AgentEvent types +21/-0

New: compile-time interface satisfaction test for all AgentEvent types

• Adds a single test that appends all 8 concrete event types to an '[]AgentEvent' slice, verifying at compile time that each satisfies the interface.

internal/runtime/event_test.go

renderer_test.goNew: comprehensive unit tests for EventRenderer covering all event types +225/-0

New: comprehensive unit tests for EventRenderer covering all event types

• Tests each event type handler, duplicate init suppression, block transitions (thinking→tool), CI '::notice::' annotation, result metrics population, error/retry rendering, and token event display.

internal/runtime/renderer_test.go

claude_progress_test.goUpdate: align existing tests with new behavior; add parseClaudeStream test suite +327/-51

Update: align existing tests with new behavior; add parseClaudeStream test suite

• Updates 'TestExtractBinaryName' → 'TestCollapseCommand' with full-command expectations. Updates 'TestExtractSafeContext' to reflect that Bash commands are no longer redacted. Renames 'TestProgressParserIgnoresStreamEvents' → 'TestProgressParserStreamEventsProcessed'. Removes 'TestProgressParserUnknownToolAllowlisted' and 'TestProgressParserUnknownToolAssistantNoContext' (replaced by new behavior). Adds 12 new 'TestParseClaudeStream*' tests covering all event types, token throttling, and assistant fallback deduplication.

internal/runtime/claude_progress_test.go

Documentation (2) +1819 / -0
2026-07-06-agent-event-stream-rendering-design.mdNew: design spec for agent event stream rendering +304/-0

New: design spec for agent event stream rendering

• Documents the three-layer architecture (normalized event types, runtime-specific parser, EventRenderer), event type table, Claude stream parser mapping, renderer rendering table, ClaudeRuntime wiring, information disclosure considerations, and future opencode integration path.

docs/superpowers/specs/2026-07-06-agent-event-stream-rendering-design.md

2026-07-06-agent-event-stream-rendering.mdNew: six-task TDD implementation plan for agent event stream rendering +1515/-0

New: six-task TDD implementation plan for agent event stream rendering

• Step-by-step implementation plan with code blocks, test commands, and commit messages for each task: event types, OnEvent callback, EventRenderer, Claude parser refactor, ClaudeRuntime wiring, and lint/verification.

docs/superpowers/plans/2026-07-06-agent-event-stream-rendering.md

Other (2) +216 / -0
event.goNew: normalized AgentEvent interface and 8 concrete event types +82/-0

New: normalized AgentEvent interface and 8 concrete event types

• Defines the 'AgentEvent' sealed interface with a no-op marker method and 8 concrete structs: 'InitEvent', 'ThinkingEvent', 'TextEvent', 'ToolUseEvent', 'TokensEvent', 'ResultEvent', 'ErrorEvent', 'RetryEvent'. These serve as the runtime-agnostic bridge between Claude's NDJSON parser and the renderer.

internal/runtime/event.go

renderer.goNew: EventRenderer that renders AgentEvent values to terminal with block state tracking +134/-0

New: EventRenderer that renders AgentEvent values to terminal with block state tracking

• Implements 'EventRenderer' with a 'Handle(AgentEvent)' dispatcher. Suppresses duplicate 'InitEvent' headers via 'seenInit' flag, renders thinking (italic/dim 🧠) and text (💬) as streaming deltas with block state tracking, emits tool calls via 'printer.ToolProgress' (blue ⚙), logs token stats, and populates 'RunMetrics' from 'ResultEvent'. CI mode emits '::notice::' annotations to stderr for tool calls.

internal/runtime/renderer.go

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 6, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 9:57 PM UTC · Ended 9:58 PM UTC
Commit: e8381e3 · View workflow run →

@qodo-code-review

qodo-code-review Bot commented Jul 6, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 54 rules

Grey Divider


Action required

1. Command context leaks secrets ✓ Resolved 🐞 Bug ⛨ Security
Description
extractSafeContext now returns the full Bash command (and Agent prompt) via collapseCommand, so
secrets embedded in env var assignments or arguments can be printed to terminal output and CI
annotations. This is a direct information disclosure risk in CI logs (e.g., API keys/tokens in
commands).
Code

internal/runtime/claude_progress.go[R360-363]

		if err := json.Unmarshal(raw, &cmd); err != nil {
			return ""
		}
-		return extractBinaryName(cmd)
+		return collapseCommand(cmd)
Relevance

⭐⭐⭐ High

Repo historically avoids leaking tool args; progress context was “safe” (binary-only) in #284, so
full-command logging likely rejected.

PR-#284

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Bash context now returns the whole command string (not just a binary), and tests demonstrate that
secret-looking values are expected to appear in the extracted context.

internal/runtime/claude_progress.go[342-405]
internal/runtime/claude_progress_test.go[42-66]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Tool context extraction now displays full Bash commands and Agent prompts. These strings often contain secrets (env vars like `API_KEY=...`, tokens in headers, etc.) and will be rendered to logs/CI notices.

## Issue Context
Tests explicitly validate that an `API_KEY=secret123 ...` prefix is preserved by `extractSafeContext`, confirming the new behavior can surface secrets.

## Fix Focus Areas
- Restore a safer default for Bash context (e.g., binary-only), OR
- Implement redaction for common secret patterns before returning command/prompt strings:
 - redact `KEY=VALUE` where KEY matches `(?i)(token|key|secret|password|auth)`
 - redact common flags (`--token`, `--password`, `Authorization:` header values)
- Keep truncation behavior after redaction.
- Update tests to assert secrets are redacted.

### Code pointers
- internal/runtime/claude_progress.go[342-425]
- internal/runtime/claude_progress_test.go[42-66]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Untrusted text not sanitized ✓ Resolved 🐞 Bug ⛨ Security
Description
EventRenderer prints ThinkingEvent/TextEvent (and the InitEvent label) directly to the terminal via
Printer.Raw/Header without sanitizeOutput, allowing ANSI/control sequences or GitHub Actions
workflow command markers from untrusted model output to reach logs. This can enable log/annotation
injection and make CI output misleading or unsafe.
Code

internal/runtime/renderer.go[R41-66]

+	case InitEvent:
+		if r.seenInit {
+			return
+		}
+		r.seenInit = true
+		r.endBlock()
+		label := e.Model
+		if e.Version != "" {
+			label = fmt.Sprintf("%s (v%s)", e.Model, e.Version)
+		}
+		r.printer.Header("Agent: " + label)
+	case ThinkingEvent:
+		if !r.inThinking {
+			r.endBlock()
+			r.printer.Raw(thinkingStyle.Render("  \U0001f9e0 "))
+			r.inThinking = true
+		}
+		r.printer.Raw(thinkingStyle.Render(e.Text))
+	case TextEvent:
+		if !r.inText {
+			r.endBlock()
+			r.printer.Raw("  \U0001f4ac ")
+			r.inText = true
+		}
+		r.printer.Raw(e.Text)
+	case ToolUseEvent:
Relevance

⭐⭐⭐ High

Team previously pushed sanitizing untrusted output to prevent GHA/ANSI injection (accepted in #764;
partially in #284).

PR-#764
PR-#284

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Renderer emits model-provided text via Raw without sanitization, while the repo already has a
dedicated sanitization function for untrusted sandbox output (including GHA workflow command
markers).

internal/runtime/renderer.go[39-66]
internal/runtime/sanitize.go[11-29]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`EventRenderer.Handle` writes untrusted model text (thinking/text deltas) directly via `printer.Raw(...)` without calling `sanitizeOutput`, bypassing existing defenses against ANSI/control characters and GHA workflow command markers.

## Issue Context
`sanitizeOutput` is explicitly intended to strip ANSI sequences, control characters, and GitHub Actions workflow command markers from **untrusted sandbox output**. Tool rendering already uses it, but thinking/text does not.

## Fix Focus Areas
- Apply `sanitizeOutput` to all untrusted strings before rendering:
 - ThinkingEvent.Text
 - TextEvent.Text
 - InitEvent.Model / InitEvent.Version (defense-in-depth)
- Keep renderer styling by sanitizing *before* applying lipgloss style.

### Code pointers
- internal/runtime/renderer.go[39-66]
- internal/runtime/sanitize.go[11-29]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. ToolProgress() lacks unit test ✓ Resolved 📘 Rule violation ▣ Testability
Description
The PR adds Printer.ToolProgress() and changes Printer.Heartbeat() output, but no tests were
added/updated to exercise these behaviors. This violates the requirement to include tests for
modified Go logic and increases risk of regressions in CLI rendering.
Code

internal/ui/ui.go[R140-148]

+	styled := lipgloss.NewStyle().Foreground(ColorMuted).Render("⏳ " + text)
+	fmt.Fprintf(p.w, "  %s\n", styled)
+}
+
+// ToolProgress prints a tool invocation progress line.
+func (p *Printer) ToolProgress(text string) {
+	p.mu.Lock()
+	defer p.mu.Unlock()
+	styled := lipgloss.NewStyle().Foreground(ColorInfo).Render("⚙ " + text)
Relevance

⭐⭐ Medium

Mixed history on test-coverage enforcement: UI output changes had tests updated in #1493, but
coverage requests were rejected in #2860.

PR-#1493
PR-#2860

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1062049 requires tests for new or modified Go logic. The diff adds a new
ToolProgress() method and changes Heartbeat() output in internal/ui/ui.go, but the existing UI
tests do not include coverage for these methods.

Rule 1062049: Require tests for new or modified Go logic
internal/ui/ui.go[140-148]
internal/ui/ui_test.go[1-124]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`internal/ui/ui.go` introduces a new `ToolProgress()` method and changes the `Heartbeat()` glyph, but the UI package tests do not cover these new/changed behaviors.

## Issue Context
Compliance requires tests for new or modified Go logic. This change affects user-visible terminal output and should be protected by unit tests in the `internal/ui` package.

## Fix Focus Areas
- internal/ui/ui.go[140-148]
- internal/ui/ui_test.go[1-124]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. OnEvent bypasses RunMetrics ✓ Resolved 🐞 Bug ≡ Correctness
Description
When RunParams.OnEvent is provided, ClaudeRuntime.Run passes it directly to parseClaudeStream and
skips the default wrapper that populates RunMetrics (model + tool count + result stats). This yields
missing/incorrect metrics for callers using custom event handlers and can break downstream telemetry
aggregation.
Code

internal/runtime/claude.go[R108-119]

+	handler := params.OnEvent
+	if handler == nil {
+		renderer := NewEventRenderer(printer, start, metrics)
+		handler = func(evt AgentEvent) {
+			if init, ok := evt.(InitEvent); ok && metrics.Model == "" {
+				metrics.Model = init.Model
+			}
+			renderer.Handle(evt)
+		}
+	}
+
+	if parseErr := parseClaudeStream(r, handler); parseErr != nil {
Relevance

⭐⭐ Medium

No close precedent for OnEvent needing metrics wrapper; team cares about metrics though (metrics
persistence accepted in #1682).

PR-#1682

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The default path wraps events to set metrics.Model and render (which also updates tool/result
metrics); the custom handler path skips that wrapper entirely, and RunMetrics is expected to be
collected from parsing.

internal/runtime/claude.go[89-123]
internal/runtime/runtime.go[11-34]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Providing a custom `RunParams.OnEvent` currently disables the default metrics-populating behavior (and model capture), because the wrapper is only installed when `OnEvent == nil`.

## Issue Context
`RunMetrics` is documented as collecting execution statistics from stream parsing, but `parseClaudeStream` only emits events; metrics mutation is done by the default renderer path.

## Fix Focus Areas
- Always update `metrics` regardless of whether `params.OnEvent` is set:
 - On `InitEvent`: set `metrics.Model` if empty
 - On `ToolUseEvent`: increment `metrics.ToolCalls`
 - On `ResultEvent`: set cost/tokens/turns/cache fields
- Then forward the event to the caller-provided handler.

### Code pointers
- internal/runtime/claude.go[89-123]
- internal/runtime/runtime.go[11-34]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread internal/ui/ui.go
Comment thread internal/runtime/renderer.go
Comment thread internal/runtime/claude_progress.go
Comment thread internal/runtime/claude.go
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 6, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:59 PM UTC · Completed 10:10 PM UTC
Commit: e53532a · View workflow run →

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.39100% with 48 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/runtime/claude_progress.go 83.72% 18 Missing and 10 partials ⚠️
internal/runtime/claude.go 0.00% 12 Missing ⚠️
internal/runtime/event.go 0.00% 8 Missing ⚠️

📢 Thoughts on this report? Let us know!

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review

Verdict: Approve

This is a re-review following commit 9d6ee4e7 which removes the implementation plan file docs/superpowers/plans/2026-07-06-agent-event-stream-rendering.md (1515 lines). The removal aligns with repository policy — plan files are gitignored (docs/superpowers/plans/*.md in .gitignore) and are not kept in the repo. No code changes occurred in this commit.

The overall PR — normalized AgentEvent types, Claude-specific NDJSON parser, runtime-agnostic EventRenderer — remains well-designed across six review iterations. All prior critical, high, and medium findings were resolved.

Changes since prior review

Commit 9d6ee4e7 makes one change:

  1. Plan file removal: Deletes docs/superpowers/plans/2026-07-06-agent-event-stream-rendering.md. No references to this file exist elsewhere in the repository. The design spec (docs/superpowers/specs/2026-07-06-agent-event-stream-rendering-design.md) is a separate file and remains in the PR.

Prior findings status

# Prior finding Status
1 ThinkingEvent/TextEvent sanitization ✅ Fixed (commit f4903b25)
2 Dead start parameter ✅ Fixed (commit f4903b25)
3 Custom OnEvent skips metrics ✅ Fixed (commit f4903b25)
4 collapseCommand secret exposure ⚠ Partially fixed — env vars redacted, non-env-var args shown (conscious trade-off)
5 Design spec stale references ✅ Fixed (commit 0141bbee)
6 Stale ToolUseEvent doc comment ✅ Fixed (commit 8e1f5db8)
7 server_tool_use not in fallback path ✅ Fixed (commit b2971172)
8 Duplicated metrics handler logic ✅ Fixed (commit b2971172)
9 Missing server_tool_use test coverage ❌ Still present (low)
10 e.Subtype unsanitized in renderer ✅ Fixed (commit 0141bbee)

Remaining low-severity finding

Missing server_tool_use test coverage — No test exercises the server_tool_use content block type in either the stream event path or the assistant fallback path. All existing tests use "tool_use" exclusively. If a future refactor accidentally removes "server_tool_use" from one of the case branches, no test will catch it.


Labels: component/runner already applied — no additional labels needed.

Previous run

Review

Verdict: Approve

This is a re-review following commit 0141bbee which sanitizes ResultEvent.Subtype in the renderer and updates the design spec to match the implementation. Both changes are correct and address the prior review's remaining fixable findings. The overall PR — normalized AgentEvent types, Claude-specific NDJSON parser, runtime-agnostic EventRenderer — remains well-designed across five review iterations.

Changes since prior review

Commit 0141bbee makes two focused changes:

  1. e.Subtype sanitization: Adds sanitizeOutput() to both e.Subtype interpolation sites in renderer.go (lines 88 and 93). The Subtype originates from the Claude API's result event and is now treated consistently with other untrusted fields (e.ErrorType, e.Message, e.Error, e.Model, e.Version, e.Text).

  2. Design spec updates: Corrects three stale claims in docs/superpowers/specs/2026-07-06-agent-event-stream-rendering-design.md: (a) line 163 area now accurately describes collapseCommand replacing extractBinaryName and the removal of allowedTools; (b) line 253 area now correctly states the allowedTools blocklist was removed with env var redaction via collapseCommand; (c) line 289 area now correctly describes the testing strategy (sanitizeOutput + collapseCommand, not allowedTools filtering).

Prior findings status

# Prior finding Status
1 ThinkingEvent/TextEvent sanitization ✅ Fixed (commit f4903b25)
2 Dead start parameter ✅ Fixed (commit f4903b25)
3 Custom OnEvent skips metrics ✅ Fixed (commit f4903b25)
4 collapseCommand secret exposure ⚠ Partially fixed — env vars redacted, non-env-var args shown (conscious trade-off)
5 Design spec stale references ✅ Fixed (commit 0141bbee)
6 Stale ToolUseEvent doc comment ✅ Fixed (commit 8e1f5db8)
7 server_tool_use not in fallback path ✅ Fixed (commit b2971172)
8 Duplicated metrics handler logic ✅ Fixed (commit b2971172)
9 Missing server_tool_use test coverage ❌ Still present (low)
10 e.Subtype unsanitized in renderer ✅ Fixed (commit 0141bbee)

Remaining low-severity finding

Missing server_tool_use test coverage — No test exercises the server_tool_use content block type in either the stream event path (claude_progress.go line 186) or the assistant fallback path (line 268). All existing tests use "tool_use" exclusively. If a future refactor accidentally removes "server_tool_use" from one of the case branches, no test will catch it.


Labels: component/runner already applied — no additional labels needed.

Previous run (2)

Review

Verdict: Approve

This is a re-review following commit b2971172 which consolidates the duplicated metrics handler logic into RunMetrics.Update() and adds server_tool_use handling to the assistant fallback path. Both changes are clean and correct. The overall PR architecture — normalized events, runtime-specific parser, runtime-agnostic renderer — is well-designed, and the iterative feedback loop across four review rounds has resolved all critical and medium findings.

Changes since prior review

Commit b2971172 makes three focused changes:

  1. Metrics consolidation: Extracts the duplicated metricsHandler closure from claude.go and inline metrics handling from progressParser into a single RunMetrics.Update() method on runtime.go. Both call sites now use metrics.Update(evt), eliminating the divergence risk flagged in the prior review.

  2. server_tool_use fallback handling: Adds "server_tool_use" alongside "tool_use" in the assistant fallback path's content item switch (line 268 of claude_progress.go). This matches the stream event path (line 180) which already handled both types. Both paths now consistently handle server_tool_use.

  3. progressParser wrapper: Updated to use metrics.Update(evt) instead of its own inline metrics handling, making it consistent with the production path in claude.go.

Prior findings status

# Prior finding Status
1 ThinkingEvent/TextEvent sanitization ✅ Fixed (commit f4903b25)
2 Dead start parameter ✅ Fixed (commit f4903b25)
3 Custom OnEvent skips metrics ✅ Fixed (commit f4903b25)
4 collapseCommand secret exposure ⚠ Partially fixed — env vars redacted, non-env-var args shown (conscious trade-off)
5 Design spec stale references ❌ Still present (low)
6 Stale ToolUseEvent doc comment ✅ Fixed (commit 8e1f5db8)
7 server_tool_use not in fallback path ✅ Fixed (commit b2971172)
8 Duplicated metrics handler logic ✅ Fixed (commit b2971172)
9 Missing server_tool_use test coverage ❌ Still present (low)

Remaining low-severity findings

Design spec stale references — The spec at docs/superpowers/specs/2026-07-06-agent-event-stream-rendering-design.md still contains three claims that contradict the implementation: (1) line 163 references extractBinaryName and allowedTools as "reused" — extractBinaryName was replaced by collapseCommand and allowedTools was removed; (2) line 250 states "The existing allowedTools safeguard carries over unchanged" — it was removed; (3) line 284 references allowedTools filtering in testing — no such tests exist. Since the spec is a Draft introduced in this PR, it should be updated to match the implementation.

Missing server_tool_use test coverage — No test exercises the server_tool_use content block type in either the stream path or the assistant fallback path. All existing tests use "tool_use" exclusively. If a future refactor accidentally removes "server_tool_use" from one of the case branches, there is no test to catch it.

e.Subtype unsanitized in ResultEvent label — In renderer.go, the ResultEvent rendering interpolates e.Subtype into the header label (fmt.Sprintf("Result: ERROR (%s)", e.Subtype)) without passing through sanitizeOutput. The Subtype originates from the Claude API's result event. While it flows through printer.Header (not directly to ::notice:: annotations), applying sanitizeOutput would be consistent with the treatment of other untrusted fields.


Labels: component/runner already applied — no additional labels needed.

Previous run (3)

Review

Verdict: Comment

This is a re-review following commit 8e1f5db8 which fixes the stale ToolUseEvent doc comment flagged in the prior review. The overall architecture remains sound — three clean layers (normalized events, runtime-specific parser, runtime-agnostic renderer), comprehensive test coverage (87+ tests), and proper sanitization on all output paths. The latest commit resolves the last of the three fully-fixable findings from the prior review.

Changes since prior review

The author's response commit (8e1f5db8) fixes the stale ToolUseEvent doc comment. The comment now correctly reads "Name is the raw tool name from the runtime" and "Summary is empty for tools not recognized by extractSafeContext" — matching the implementation where allowedTools was removed.

Prior findings status

# Prior finding Status
1 ThinkingEvent/TextEvent sanitization ✅ Fixed (commit f4903b25)
2 Dead start parameter ✅ Fixed (commit f4903b25)
3 Custom OnEvent skips metrics ✅ Fixed (commit f4903b25)
4 collapseCommand secret exposure ⚠ Partially fixed — env vars redacted, non-env-var args still shown
5 Design spec not updated ❌ Still present
6 Stale ToolUseEvent doc comment ✅ Fixed (commit 8e1f5db8)

Remaining findings

Design spec inaccurately describes allowedTools and extractBinaryName — The design spec at docs/superpowers/specs/2026-07-06-agent-event-stream-rendering-design.md contains three claims that contradict the implementation: (1) line 163 states extractSafeContext, extractBinaryName, and allowedTools are reused — but extractBinaryName was replaced by collapseCommand and allowedTools was removed; (2) line 250 states "The existing allowedTools safeguard carries over unchanged" — it was removed entirely; (3) line 284 references allowedTools filtering in the testing strategy — no such tests exist. Since the spec was introduced in this same PR, it should reflect the actual implementation.

server_tool_use not handled in assistant fallback path — The stream_event path at line 186 handles both tool_use and server_tool_use content block types, but the assistant fallback path at line 317 only matches "tool_use". If an older Claude Code version emits a complete assistant message containing a server_tool_use block (instead of streaming), that tool invocation will be silently dropped. This is an edge case — server_tool_use via the non-streaming path is rare — but the inconsistency is worth noting.

Duplicated metrics handler logic — The progressParser wrapper (lines 333-350) duplicates the metricsHandler closure from claude.go (lines 110-126). progressParser is no longer called by production code — only by tests. If a new event type is added to one but not the other, the test path and production path will silently diverge.

Missing test coverage for server_tool_use and Agent tool — No test exercises the server_tool_use content block type in either the stream path or the fallback path. The Agent tool case in extractSafeContext (line 391) also lacks a dedicated test — it calls collapseCommand on the prompt field but no test fixture exercises this.

Previous run (4)

Review

Verdict: Comment

This is a re-review following the author's response commit (f4903b25) which addresses the prior review's feedback. The latest commit resolves three of the prior findings fully, partially mitigates the highest-severity finding, and leaves two low-severity documentation issues. The overall design remains sound — well-layered architecture with normalized events, callback-based integration, and clean separation of parsing/rendering/metrics.

Changes since prior review

The author's response commit addresses the prior review directly:

  1. ThinkingEvent/TextEvent sanitization (prior low) → Fully fixed. Both events now pass through sanitizeOutput before rendering. Tests (TestRendererSanitizesThinkingText, TestRendererSanitizesTextEvent) verify GHA marker and ANSI escape stripping.

  2. Dead start parameter (prior medium) → Fully fixed. NewEventRenderer no longer accepts start time.Time; the struct has no start field.

  3. Custom OnEvent skips metrics (prior low) → Fully fixed. A metricsHandler wrapper is now applied to both default and custom event handlers, ensuring RunMetrics is always populated regardless of callback.

  4. collapseCommand secret exposure (prior high) → Partially fixed. Leading KEY=VALUE env var assignments are now redacted via redactEnvVar. However, non-env-var command arguments are still shown verbatim (see remaining finding below).

Remaining findings

collapseCommand still exposes non-env-var secrets in command arguments — The redactEnvVar mitigation handles the most common secret leakage vector (env vars), but inline secrets in command arguments pass through. The test at TestExtractSafeContext("bash with args") explicitly expects curl -H 'Bearer token123' https://api.example.com as output — the bearer token is visible. The old extractBinaryName returned only "curl". This output flows to ::notice:: CI annotations and terminal. While the risk is lower than the prior review's assessment (env vars are now handled, sandbox provides isolation, and log readers already have repo access), this remains a regression from the prior information-minimization posture. Consider whether bare binary name + env-var-redacted args would provide a better security/debuggability tradeoff (e.g., redacting all flag values, not just leading env vars).

Design spec not updated to reflect allowedTools removal — The spec at docs/superpowers/specs/2026-07-06-agent-event-stream-rendering-design.md states "The existing allowedTools safeguard carries over unchanged" and "unknown tools get name "tool" and empty summary." The implementation removes allowedTools entirely — all tool names pass through, and the commit message explains the rationale ("tool names are not sensitive and the trajectory is public"). The spec should be updated to match.

Stale ToolUseEvent doc comment — The doc comment on ToolUseEvent in event.go still reads Name is the tool name ("tool" for unknown/non-allowlisted tools), but the allowlist no longer exists. All tool names pass through as-is; only extractSafeContext returns empty for tools it doesn't recognize.

Previous run (5)

Review

Verdict: Request changes

This PR implements a well-structured event stream rendering architecture for fullsend run — normalized AgentEvent types, a Claude-specific NDJSON parser, and a runtime-agnostic EventRenderer. The layered design is clean and the test coverage is substantial (87 tests). However, there are security regressions in the information disclosure posture that need resolution before merge.

High-severity findings

collapseCommand exposes secrets in Bash command argumentsextractBinaryName was deliberately designed to return only the binary name (e.g., "make" from "API_KEY=secret123 make test"). The replacement collapseCommand returns the full command up to 120 characters, including inline secrets, environment variable assignments, and auth tokens. The test updates confirm this is intentional (want: "API_KEY=secret123 curl https://api.example.com"). This output flows to GHA ::notice:: annotations visible to anyone with repo read access. This is a regression from an explicit security control.

Medium-severity findings

  1. allowedTools guard removed despite design spec — The design spec included in this PR states "The existing allowedTools safeguard carries over unchanged" and "unknown tools get name "tool" and empty summary." But the implementation removes allowedTools entirely. All tool names from untrusted sandbox output are now displayed verbatim.

  2. Dead start parameter in NewEventRenderer — The constructor accepts start time.Time but never stores it (the struct has no start field). The plan document shows elapsed time in tool progress, but the implementation omits it. This is dead code and a plan-implementation mismatch.

  3. Stale ToolUseEvent doc comment — The doc comment says Name is the tool name ("tool" for unknown/non-allowlisted tools) but the implementation passes real tool names through without masking.

Low-severity findings

  1. Test integrity concernTestProgressParserUnknownToolAllowlisted was renamed and its assertion inverted to match the guard removal. The companion test TestProgressParserUnknownToolAssistantNoContext was deleted. Security-relevant test assertions were weakened alongside the production change.

  2. Custom OnEvent skips model capture — When params.OnEvent is caller-supplied, the model-capture logic (metrics.Model = init.Model) is not applied. This is an undocumented requirement for custom handlers.

  3. ThinkingEvent/TextEvent bypass sanitizeOutput — These events are rendered via printer.Raw() without sanitization. If a prompt-injected model emits GHA workflow commands (e.g., ::error::...) in thinking text, they could create spurious annotations. The ToolUseEvent path correctly sanitizes.

Previous run (6)

Review

Verdict: Comment

This is a re-review following commit 8e1f5db8 which fixes the stale ToolUseEvent doc comment flagged in the prior review. The overall architecture remains sound — three clean layers (normalized events, runtime-specific parser, runtime-agnostic renderer), comprehensive test coverage (87+ tests), and proper sanitization on all output paths. The latest commit resolves the last of the three fully-fixable findings from the prior review.

Changes since prior review

The author's response commit (8e1f5db8) fixes the stale ToolUseEvent doc comment. The comment now correctly reads "Name is the raw tool name from the runtime" and "Summary is empty for tools not recognized by extractSafeContext" — matching the implementation where allowedTools was removed.

Prior findings status

# Prior finding Status
1 ThinkingEvent/TextEvent sanitization ✅ Fixed (commit f4903b25)
2 Dead start parameter ✅ Fixed (commit f4903b25)
3 Custom OnEvent skips metrics ✅ Fixed (commit f4903b25)
4 collapseCommand secret exposure ⚠ Partially fixed — env vars redacted, non-env-var args still shown
5 Design spec not updated ❌ Still present
6 Stale ToolUseEvent doc comment ✅ Fixed (commit 8e1f5db8)

Remaining findings

Design spec inaccurately describes allowedTools and extractBinaryName — The design spec at docs/superpowers/specs/2026-07-06-agent-event-stream-rendering-design.md contains three claims that contradict the implementation: (1) line 163 states extractSafeContext, extractBinaryName, and allowedTools are reused — but extractBinaryName was replaced by collapseCommand and allowedTools was removed; (2) line 250 states "The existing allowedTools safeguard carries over unchanged" — it was removed entirely; (3) line 284 references allowedTools filtering in the testing strategy — no such tests exist. Since the spec was introduced in this same PR, it should reflect the actual implementation.

server_tool_use not handled in assistant fallback path — The stream_event path at line 186 handles both tool_use and server_tool_use content block types, but the assistant fallback path at line 317 only matches "tool_use". If an older Claude Code version emits a complete assistant message containing a server_tool_use block (instead of streaming), that tool invocation will be silently dropped. This is an edge case — server_tool_use via the non-streaming path is rare — but the inconsistency is worth noting.

Duplicated metrics handler logic — The progressParser wrapper (lines 333-350) duplicates the metricsHandler closure from claude.go (lines 110-126). progressParser is no longer called by production code — only by tests. If a new event type is added to one but not the other, the test path and production path will silently diverge.

Missing test coverage for server_tool_use and Agent tool — No test exercises the server_tool_use content block type in either the stream path or the fallback path. The Agent tool case in extractSafeContext (line 391) also lacks a dedicated test — it calls collapseCommand on the prompt field but no test fixture exercises this.


Labels: component/runner already applied — no additional labels needed.

Previous run (7)

Review

Verdict: Comment

This is a re-review following the author's response commit (f4903b25) which addresses the prior review's feedback. The latest commit resolves three of the prior findings fully, partially mitigates the highest-severity finding, and leaves two low-severity documentation issues. The overall design remains sound — well-layered architecture with normalized events, callback-based integration, and clean separation of parsing/rendering/metrics.

Changes since prior review

The author's response commit addresses the prior review directly:

  1. ThinkingEvent/TextEvent sanitization (prior low) → Fully fixed. Both events now pass through sanitizeOutput before rendering. Tests (TestRendererSanitizesThinkingText, TestRendererSanitizesTextEvent) verify GHA marker and ANSI escape stripping.

  2. Dead start parameter (prior medium) → Fully fixed. NewEventRenderer no longer accepts start time.Time; the struct has no start field.

  3. Custom OnEvent skips metrics (prior low) → Fully fixed. A metricsHandler wrapper is now applied to both default and custom event handlers, ensuring RunMetrics is always populated regardless of callback.

  4. collapseCommand secret exposure (prior high) → Partially fixed. Leading KEY=VALUE env var assignments are now redacted via redactEnvVar. However, non-env-var command arguments are still shown verbatim (see remaining finding below).

Remaining findings

collapseCommand still exposes non-env-var secrets in command arguments — The redactEnvVar mitigation handles the most common secret leakage vector (env vars), but inline secrets in command arguments pass through. The test at TestExtractSafeContext("bash with args") explicitly expects curl -H 'Bearer token123' https://api.example.com as output — the bearer token is visible. The old extractBinaryName returned only "curl". This output flows to ::notice:: CI annotations and terminal. While the risk is lower than the prior review's assessment (env vars are now handled, sandbox provides isolation, and log readers already have repo access), this remains a regression from the prior information-minimization posture. Consider whether bare binary name + env-var-redacted args would provide a better security/debuggability tradeoff (e.g., redacting all flag values, not just leading env vars).

Design spec not updated to reflect allowedTools removal — The spec at docs/superpowers/specs/2026-07-06-agent-event-stream-rendering-design.md states "The existing allowedTools safeguard carries over unchanged" and "unknown tools get name "tool" and empty summary." The implementation removes allowedTools entirely — all tool names pass through, and the commit message explains the rationale ("tool names are not sensitive and the trajectory is public"). The spec should be updated to match.

Stale ToolUseEvent doc comment — The doc comment on ToolUseEvent in event.go still reads Name is the tool name ("tool" for unknown/non-allowlisted tools), but the allowlist no longer exists. All tool names pass through as-is; only extractSafeContext returns empty for tools it doesn't recognize.


Labels: component/runner already applied — no additional labels needed.

Review

Verdict: Request changes

This PR implements a well-structured event stream rendering architecture for fullsend run — normalized AgentEvent types, a Claude-specific NDJSON parser, and a runtime-agnostic EventRenderer. The layered design is clean and the test coverage is substantial (87 tests). However, there are security regressions in the information disclosure posture that need resolution before merge.

High-severity findings

collapseCommand exposes secrets in Bash command argumentsextractBinaryName was deliberately designed to return only the binary name (e.g., "make" from "API_KEY=secret123 make test"). The replacement collapseCommand returns the full command up to 120 characters, including inline secrets, environment variable assignments, and auth tokens. The test updates confirm this is intentional (want: "API_KEY=secret123 curl https://api.example.com"). This output flows to GHA ::notice:: annotations visible to anyone with repo read access. This is a regression from an explicit security control.

Medium-severity findings

  1. allowedTools guard removed despite design spec — The design spec included in this PR states "The existing allowedTools safeguard carries over unchanged" and "unknown tools get name "tool" and empty summary." But the implementation removes allowedTools entirely. All tool names from untrusted sandbox output are now displayed verbatim.

  2. Dead start parameter in NewEventRenderer — The constructor accepts start time.Time but never stores it (the struct has no start field). The plan document shows elapsed time in tool progress, but the implementation omits it. This is dead code and a plan-implementation mismatch.

  3. Stale ToolUseEvent doc comment — The doc comment says Name is the tool name ("tool" for unknown/non-allowlisted tools) but the implementation passes real tool names through without masking.

Low-severity findings

  1. Test integrity concernTestProgressParserUnknownToolAllowlisted was renamed and its assertion inverted to match the guard removal. The companion test TestProgressParserUnknownToolAssistantNoContext was deleted. Security-relevant test assertions were weakened alongside the production change.

  2. Custom OnEvent skips model capture — When params.OnEvent is caller-supplied, the model-capture logic (metrics.Model = init.Model) is not applied. This is an undocumented requirement for custom handlers.

  3. ThinkingEvent/TextEvent bypass sanitizeOutput — These events are rendered via printer.Raw() without sanitization. If a prompt-injected model emits GHA workflow commands (e.g., ::error::...) in thinking text, they could create spurious annotations. The ToolUseEvent path correctly sanitizes.


Labels: PR modifies internal/runtime event streaming and internal/ui printer

Previous run (8)

Review

Verdict: Comment

This is a re-review following the author's response commit (f4903b25) which addresses the prior review's feedback. The latest commit resolves three of the prior findings fully, partially mitigates the highest-severity finding, and leaves two low-severity documentation issues. The overall design remains sound — well-layered architecture with normalized events, callback-based integration, and clean separation of parsing/rendering/metrics.

Changes since prior review

The author's response commit addresses the prior review directly:

  1. ThinkingEvent/TextEvent sanitization (prior low) → Fully fixed. Both events now pass through sanitizeOutput before rendering. Tests (TestRendererSanitizesThinkingText, TestRendererSanitizesTextEvent) verify GHA marker and ANSI escape stripping.

  2. Dead start parameter (prior medium) → Fully fixed. NewEventRenderer no longer accepts start time.Time; the struct has no start field.

  3. Custom OnEvent skips metrics (prior low) → Fully fixed. A metricsHandler wrapper is now applied to both default and custom event handlers, ensuring RunMetrics is always populated regardless of callback.

  4. collapseCommand secret exposure (prior high) → Partially fixed. Leading KEY=VALUE env var assignments are now redacted via redactEnvVar. However, non-env-var command arguments are still shown verbatim (see remaining finding below).

Remaining findings

collapseCommand still exposes non-env-var secrets in command arguments — The redactEnvVar mitigation handles the most common secret leakage vector (env vars), but inline secrets in command arguments pass through. The test at TestExtractSafeContext("bash with args") explicitly expects curl -H 'Bearer token123' https://api.example.com as output — the bearer token is visible. The old extractBinaryName returned only "curl". This output flows to ::notice:: CI annotations and terminal. While the risk is lower than the prior review's assessment (env vars are now handled, sandbox provides isolation, and log readers already have repo access), this remains a regression from the prior information-minimization posture. Consider whether bare binary name + env-var-redacted args would provide a better security/debuggability tradeoff (e.g., redacting all flag values, not just leading env vars).

Design spec not updated to reflect allowedTools removal — The spec at docs/superpowers/specs/2026-07-06-agent-event-stream-rendering-design.md states "The existing allowedTools safeguard carries over unchanged" and "unknown tools get name "tool" and empty summary." The implementation removes allowedTools entirely — all tool names pass through, and the commit message explains the rationale ("tool names are not sensitive and the trajectory is public"). The spec should be updated to match.

Stale ToolUseEvent doc comment — The doc comment on ToolUseEvent in event.go still reads Name is the tool name ("tool" for unknown/non-allowlisted tools), but the allowlist no longer exists. All tool names pass through as-is; only extractSafeContext returns empty for tools it doesn't recognize.


Labels: component/runner already applied — no additional labels needed.

Previous run (9)

Review

Verdict: Request changes

This PR implements a well-structured event stream rendering architecture for fullsend run — normalized AgentEvent types, a Claude-specific NDJSON parser, and a runtime-agnostic EventRenderer. The layered design is clean and the test coverage is substantial (87 tests). However, there are security regressions in the information disclosure posture that need resolution before merge.

High-severity findings

collapseCommand exposes secrets in Bash command argumentsextractBinaryName was deliberately designed to return only the binary name (e.g., "make" from "API_KEY=secret123 make test"). The replacement collapseCommand returns the full command up to 120 characters, including inline secrets, environment variable assignments, and auth tokens. The test updates confirm this is intentional (want: "API_KEY=secret123 curl https://api.example.com"). This output flows to GHA ::notice:: annotations visible to anyone with repo read access. This is a regression from an explicit security control.

Medium-severity findings

  1. allowedTools guard removed despite design spec — The design spec included in this PR states "The existing allowedTools safeguard carries over unchanged" and "unknown tools get name "tool" and empty summary." But the implementation removes allowedTools entirely. All tool names from untrusted sandbox output are now displayed verbatim.

  2. Dead start parameter in NewEventRenderer — The constructor accepts start time.Time but never stores it (the struct has no start field). The plan document shows elapsed time in tool progress, but the implementation omits it. This is dead code and a plan-implementation mismatch.

  3. Stale ToolUseEvent doc comment — The doc comment says Name is the tool name ("tool" for unknown/non-allowlisted tools) but the implementation passes real tool names through without masking.

Low-severity findings

  1. Test integrity concernTestProgressParserUnknownToolAllowlisted was renamed and its assertion inverted to match the guard removal. The companion test TestProgressParserUnknownToolAssistantNoContext was deleted. Security-relevant test assertions were weakened alongside the production change.

  2. Custom OnEvent skips model capture — When params.OnEvent is caller-supplied, the model-capture logic (metrics.Model = init.Model) is not applied. This is an undocumented requirement for custom handlers.

  3. ThinkingEvent/TextEvent bypass sanitizeOutput — These events are rendered via printer.Raw() without sanitization. If a prompt-injected model emits GHA workflow commands (e.g., ::error::...) in thinking text, they could create spurious annotations. The ToolUseEvent path correctly sanitizes.


Labels: PR modifies internal/runtime event streaming and internal/ui printer

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the component/runner Agent runner behavior and lifecycle label Jul 6, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 7, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:25 PM UTC · Completed 4:34 PM UTC
Commit: f4903b2 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot dismissed their stale review July 7, 2026 16:34

Superseded by updated review

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jul 7, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 7, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:20 PM UTC · Completed 5:30 PM UTC
Commit: 8e1f5db · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed requires-manual-review Review requires human judgment labels Jul 7, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 7, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:47 PM UTC · Completed 7:59 PM UTC
Commit: b297117 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot removed the requires-manual-review Review requires human judgment label Jul 7, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 7, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:02 PM UTC · Completed 8:09 PM UTC
Commit: 0141bbe · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge and removed ready-for-merge All reviewers approved — ready to merge labels Jul 7, 2026
@rh-hemartin

Copy link
Copy Markdown
Member

I don't see any runs on the triage agent for the issue linked (appdumpster/test-repo#25), could you provide the run URL directly?

@ralphbean

ralphbean commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

I don't see any runs on the triage agent for the issue linked (appdumpster/test-repo#25), could you provide the run URL directly?

Ah, yes. All my local testing was done with --no-post-script, but I can do one locally now after rebasing on main here.

edit: here it is: appdumpster/test-repo#25 (comment)

ralphbean added 14 commits July 8, 2026 14:21
Design for #3170. Defines normalized AgentEvent types, a callback-based
integration via RunParams.OnEvent, and an EventRenderer that produces
structured terminal output (thinking, text, tool summaries, tokens,
errors) during fullsend run execution. The Claude stream parser is
refactored to emit normalized events; the renderer is runtime-agnostic
so opencode can plug in with its own parser later.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Six-task plan covering event types, OnEvent callback, EventRenderer,
Claude parser refactor, ClaudeRuntime.Run wiring, and final verification.
TDD throughout with complete code blocks.

Part of #3170.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Define the AgentEvent interface and concrete event structs (InitEvent,
ThinkingEvent, TextEvent, ToolUseEvent, TokensEvent, ResultEvent,
ErrorEvent, RetryEvent) that bridge runtime-specific NDJSON parsing
and runtime-agnostic rendering.

Part of #3170.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Add an optional OnEvent func(AgentEvent) field to RunParams. When set,
the runtime calls it with normalized events during execution. When nil,
events are silently discarded (or the runtime uses a default renderer).

Part of #3170.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
EventRenderer consumes normalized AgentEvent values and renders
structured terminal output: thinking text (italic/dim), assistant text,
tool summaries with elapsed time and count, token usage (throttled),
result summaries, errors, and retry warnings. Tracks block state for
clean transitions between event types.

Part of #3170.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Refactor the Claude Code NDJSON parser to emit normalized AgentEvent
values via a callback instead of calling printer.Heartbeat directly.
The new parseClaudeStream function processes stream_event deltas
(thinking, text, tool input JSON), system events (init, api_retry),
result events, and errors. The old progressParser is kept as a thin
wrapper that creates an EventRenderer.

Supports assistant message fallback for older Claude Code versions that
don't emit stream_event deltas.

Part of #3170.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Wire the OnEvent callback in ClaudeRuntime.Run. When OnEvent is nil
(the default), creates an EventRenderer that renders thinking, text,
tool summaries, token usage, errors, and result summaries to the
terminal in real time. When OnEvent is set, the caller receives
normalized AgentEvent values for custom handling.

Closes #3170.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…token throttle

Fix ResultEvent construction to include the error message from the
result event's "result" field. Remove duplicate token throttling from
EventRenderer (throttling belongs only in the parser). Add tests for
error result rendering.

Part of #3170.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
- Suppress duplicate InitEvent headers (only show first with version)
- Render text and thinking content from assistant messages in the
  fallback (non-streaming) path, which were previously silently dropped
- Differentiate tool calls (⚙ blue) from heartbeat timer (⏳ muted)
  with distinct emoji and color via new ToolProgress printer method
- Show full Bash commands instead of just the binary name, truncated
  at 120 characters and collapsed to a single line
- Remove allowedTools blocklist — tool names are not sensitive and the
  trajectory is public; extractSafeContext already returns empty for
  tools it doesn't know how to summarize
- Add context extraction for Skill (skill_name) and Agent (prompt) tools

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
- Sanitize ThinkingEvent, TextEvent, and InitEvent fields through
  sanitizeOutput before rendering, preventing ANSI escape injection and
  GHA workflow command marker injection in CI logs
- Redact leading KEY=VALUE env var assignments in collapseCommand to
  prevent secrets from leaking into progress output and CI annotations
- Extract metrics population from EventRenderer into a dedicated
  metricsHandler wrapper in ClaudeRuntime.Run, ensuring RunMetrics is
  always populated regardless of whether a custom OnEvent callback is
  provided
- Simplify EventRenderer to be purely a rendering concern (no metrics
  field, no start time)
- Add unit tests for sanitization, ToolProgress, and Heartbeat

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
The allowlist was removed but the doc comment still referenced it.

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…lback

- Extract metrics population into RunMetrics.Update method, eliminating
  duplicate logic between ClaudeRuntime.Run and progressParser
- Add server_tool_use to assistant fallback path for consistency with
  the stream_event path

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…rences

- Apply sanitizeOutput to e.Subtype in renderer result label
- Update design spec: replace extractBinaryName/allowedTools references
  with collapseCommand and current behavior

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@ralphbean ralphbean force-pushed the feat/agent-event-stream-rendering branch from 0141bbe to c5d9f4d Compare July 8, 2026 19:28
Plan files are no longer kept in the repo.

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 8, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 7:29 PM UTC · Ended 7:30 PM UTC
Commit: e8381e3 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 8, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:30 PM UTC · Completed 7:38 PM UTC
Commit: 9d6ee4e · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge and removed ready-for-merge All reviewers approved — ready to merge labels Jul 8, 2026
@ralphbean ralphbean enabled auto-merge July 8, 2026 19:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/runner Agent runner behavior and lifecycle ready-for-merge All reviewers approved — ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fullsend run: render agent event stream live during execution

2 participants