Add steering message rendering in unified log view#38277
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR extends the gh aw view / gh aw audit unified timeline to include AWF API proxy steering events (token/time budget warnings) so they are visible in both table and streaming renderers, and adds test coverage for the new parsing + rendering paths. It also updates a large set of compiled workflow lockfiles to emit new “detect agent errors” outputs/env vars, expanding the PR scope beyond what the description currently states.
Changes:
- Add parsing/collection of
token_steeringandtimeout_steeringfromapi-proxy-logs/events.jsonland include them inBuildUnifiedTimeline. - Render steering events in both table and stream unified timeline outputs (icon/label + summary count).
- Add extensive unit tests for steering event parsing/collection and rendering; update many
.lock.ymlworkflows to add a “Detect agent errors” step + outputs wiring.
Show a summary per file
| File | Description |
|---|---|
| pkg/cli/gateway_logs_timeline.go | Adds steering event kind and collects/parses proxy events.jsonl into unified timeline events. |
| pkg/cli/gateway_logs_timeline_render.go | Adds steering icon/label, table/stream rendering, and includes steering counts in the unified summary. |
| pkg/cli/gateway_logs_timeline_test.go | Adds tests for steering parsing, collection, unified inclusion, and both renderers. |
| .github/workflows/unbloat-docs.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/typist.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/test-create-pr-error-handling.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/step-name-alignment.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/static-analysis-report.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/spec-enforcer.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/smoke-codex.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/smoke-claude.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/smoke-call-workflow.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/smoke-agent-scoped-approved.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/smoke-agent-public-none.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/smoke-agent-public-approved.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/smoke-agent-all-none.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/smoke-agent-all-merged.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/sergo.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/semantic-function-refactor.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/scout.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/schema-feature-coverage.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/schema-consistency-checker.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/safe-output-health.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/ruflo-backed-task.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/prompt-clustering-analysis.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/necromancer.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/lockfile-stats.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/issue-arborist.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/instructions-janitor.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/hourly-ci-cleaner.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/grumpy-reviewer.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/go-pattern-detector.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/go-logger.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/go-fan.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/github-mcp-tools-report.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/github-mcp-structural-analysis.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/example-workflow-analyzer.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/duplicate-code-detector.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/developer-docs-consolidator.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/dev.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/design-decision-gate.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/deep-report.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-token-consumption-report.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-team-evolution-insights.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-security-red-team.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-safeoutputs-git-simulator.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-safe-outputs-conformance.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-safe-output-optimizer.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-rendering-scripts-verifier.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-reliability-review.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-observability-report.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-multi-device-docs-tester.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-function-namer.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-fact.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-doc-updater.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-doc-healer.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-code-metrics.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-choice-test.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-caveman-optimizer.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-cache-strategy-analyzer.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-awf-spec-compiler-surfacing.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-aw-cross-repo-compile-check.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/daily-agentrx-trace-optimizer.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/copilot-session-insights.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/copilot-agent-analysis.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/commit-changes-analyzer.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/codex-github-remote-mcp-test.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/cloclo.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/cli-version-checker.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/claude-code-user-docs-review.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/ci-doctor.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/changeset.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/blog-auditor.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/aw-failure-investigator.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/avenger.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/audit-workflows.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/approach-validator.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/api-consumption-report.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
| .github/workflows/ai-moderator.lock.yml | Adds “Detect agent errors” step and wires new error-related outputs/env vars. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (1)
pkg/cli/gateway_logs_timeline.go:672
- BuildUnifiedTimeline sorts events purely by Time, but steering (and potentially other) events can legitimately have a zero timestamp. Since time.Time{} compares as the year 1, those events will always sort to the very start of the unified timeline, which makes the rendered chronology misleading (they show up with "-" time at the top). Consider treating zero timestamps as "unknown" and sorting them after timestamped events, and use a stable sort so equal/unknown timestamps preserve append order.
- Files reviewed: 80/80 changed files
- Comments generated: 1
| GH_AW_PROJECT_UTC: "-08:00" | ||
| GH_AW_WORKFLOW_ID_SANITIZED: unbloatdocs | ||
| outputs: | ||
| agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} |
|
✅ PR Code Quality Reviewer completed the code quality review. |
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. |
|
🧠 Matt Pocock Skills Reviewer was skipped during the skills-based review. |
|
❌ Test Quality Sentinel was skipped during test quality analysis. |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🏗️ Design Decision Gate — ADR RequiredThis PR makes significant changes to core business logic (475 new lines in 📄 Draft ADR committed:
📋 What to do next
Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision. ❓ Why ADRs MatterADRs create a searchable, permanent record of why the codebase looks the way it does — for example, why steering events reuse the firewall source and overload the 📋 Michael Nygard ADR Format ReferenceAn ADR must contain these four sections to be considered complete:
All ADRs are stored in
|
|
@copilot run pr-finisher skill |
There was a problem hiding this comment.
REQUEST_CHANGES — 1 rendering bug + 2 maintainability issues
The steering-events feature is well-structured overall (good typed struct, solid test coverage), but three issues need to be addressed before merge.
Blocking issues (3)
-
Stream indentation bug (
gateway_logs_timeline_render.go:607) — Steering events render with 2-space indent insiderenderUnifiedTimelineStream, while every other firewall-sourced event (NetworkAllowed,NetworkBlocked,DIFCFiltered,GuardPolicyBlocked) uses 4-space. Steering events will appear one visual level shallower than their peers. -
Duplicate proxy-events decoder (
gateway_logs_timeline.go:529) —proxyEventsEntry+eventName()re-implements the same field-coalescing logic already present intoken_usage.go:parseAPIProxySteeringEvents. Future format changes toapi-proxy-logs/events.jsonlmust be applied in two places. -
steeringCount/fwCountdecoupling (gateway_logs_timeline_render.go:683) — Steering events are tallied source-independently but reported only underif fwCount > 0. Works today because the source is hardcoded; fragile if that assumption ever changes.
🔎 Code quality review by PR Code Quality Reviewer · ⌖ 13.7 AIC
| case TimelineKindSteering: | ||
| icon := streamColor(styles.Warning, timelineEventIcon(TimelineKindSteering)) | ||
| msg := stringutil.Truncate(evt.Reason, streamMaxAnnotationLen) | ||
| fmt.Fprintf(&sb, " %s %s\n", icon, msg) |
There was a problem hiding this comment.
Indentation inconsistency — steering events will appear visually misaligned in stream output: " %s %s\n" uses 2-space indent while every peer firewall/gateway event case uses 4-space (" %s %s%s\n"), causing steering lines to sit one level shallower than NetworkAllowed, NetworkBlocked, DIFCFiltered, and GuardPolicyBlocked.
💡 Suggested fix
Change the format string from " %s %s\n" to " %s %s\n" (4 spaces) to match all other firewall-sourced event cases in the same switch:
case TimelineKindSteering:
icon := streamColor(styles.Warning, timelineEventIcon(TimelineKindSteering))
msg := stringutil.Truncate(evt.Reason, streamMaxAnnotationLen)
fmt.Fprintf(&sb, " %s %s\n", icon, msg) // 4 spaces, not 2For reference, TimelineKindNetworkAllowed (line ~570) and TimelineKindDIFCFiltered (line ~583) both use " %s %s%s\n". Steering events share TimelineSourceFirewall and should match that visual grouping.
| // proxyEventsEntry is a JSONL record from api-proxy-logs/events.jsonl. | ||
| // The event name appears under one of four field names depending on the proxy version; | ||
| // the message field is present on steering events. | ||
| type proxyEventsEntry struct { |
There was a problem hiding this comment.
Duplicate proxy-events parsing logic — format changes will silently diverge: proxyEventsEntry struct + eventName() re-implements the same field-coalescing that token_usage.go already does in parseAPIProxySteeringEvents via map[string]any + coalesceString. Two independent decoders for the same file means any future field name change in api-proxy-logs/events.jsonl requires a coordinated update in both files.
💡 Suggested fix
Move proxyEventsEntry and eventName() to token_usage.go (where the other proxy-parsing constants/helpers already live), then replace the map[string]any+coalesceString path in parseAPIProxySteeringEvents with the typed struct. collectSteeringTimelineEvents can then reuse the same struct:
// token_usage.go (export or share within package)
type proxyEventsEntry struct { ... } // move here from timeline.go
func (e proxyEventsEntry) eventName() string { ... }
// parseAPIProxySteeringEvents updated to use proxyEventsEntry instead of map[string]anyThis is a same-package refactor (no exported surface change) that eliminates the two-file maintenance burden.
| fmt.Fprintf(&sb, " Firewall : %d (allowed=%d, blocked=%d)\n", | ||
| fwCount, netAllowed, netBlocked) | ||
| fwDetail := fmt.Sprintf("allowed=%d, blocked=%d", netAllowed, netBlocked) | ||
| if steeringCount > 0 { |
There was a problem hiding this comment.
steeringCount is source-independent but reported only inside if fwCount > 0: the counter is incremented in the flat switch evt.Kind block (no source guard), but emitted only when fwCount > 0. Today this is harmless because steeringEntryToTimelineEvent hardcodes Source: TimelineSourceFirewall. But if any steering event ever arrives from a different source, steeringCount will be non-zero while fwCount may be zero, silently swallowing the steering tally from the summary.
💡 Suggested fix
Count steering under the firewall source bucket explicitly so the coupling is visible and consistent:
case TimelineSourceFirewall:
fwCount++
switch evt.Kind {
case TimelineKindNetworkAllowed:
netAllowed++
case TimelineKindNetworkBlocked:
netBlocked++
case TimelineKindSteering:
steeringCount++
}Or, at minimum, add a standalone if steeringCount > 0 && fwCount == 0 branch in the summary formatter so the count is never silently dropped.
…plicate proxyEventsEntry Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
@copilot merge main and recompile |
|
@copilot review all comments and address unresolved review feedback.
|
|
@copilot link the completed ADR in the PR body, then refresh the review request.
|
…-of-steering-messages Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Done. Merged |
|
@copilot review changes and apply dry / yagni / solid |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Applied in commit The main DRY violation was the near-identical JSONL scanner loop duplicated between |
AWF API proxy steering events (
token_steering,timeout_steering) were being counted inTotalSteeringEventsmetrics but silently dropped from thegh aw view/gh aw auditunified timeline.Changes
token_usage.goproxyEventsEntrystruct (handles all four event-name field variants:event,type,event_name,eventName) and itseventName()method moved here fromgateway_logs_timeline.goso both files share a single typed decoder forapi-proxy-logs/events.jsonlparseAPIProxySteeringEventsupdated to use the typed struct instead ofmap[string]any, eliminating the duplicate parsing pathgateway_logs_timeline.goTimelineKindSteeringevent kindsteeringEntryToTimelineEventvalidates against spec message prefixes ([AWF TOKEN WARNING]/[AWF TIME WARNING]); setsStatusto"token"or"time"; parses optional RFC3339 timestampcollectSteeringTimelineEventsreads fromapi-proxy-logs/events.jsonl(both canonicalsandbox/firewall/logs/and legacyfirewall-audit-logs/layouts)BuildUnifiedTimelinenow includes steering eventsgateway_logs_timeline_render.go⚠icon,"steering"label,renderSteeringRowfor table renderer⚠ <message>line at 4-space indent (consistent with all other firewall-sourced events)renderUnifiedTimelinesummary appendssteering=Nto the Firewall line when non-zero; counting loop restructured so firewall-specific counters are scoped underTimelineSourceFirewallCompiled workflow lockfiles (
*.lock.yml)make recompileafter mergingmainTests