Skip to content

[rendering-scripts] Fix Pi log parser for the v3 streaming schema (empty step summaries)#45414

Merged
pelikhan merged 4 commits into
mainfrom
fix-pi-parser-v3-schema-6ddf5857c4ec2fa8
Jul 14, 2026
Merged

[rendering-scripts] Fix Pi log parser for the v3 streaming schema (empty step summaries)#45414
pelikhan merged 4 commits into
mainfrom
fix-pi-parser-v3-schema-6ddf5857c4ec2fa8

Conversation

@github-actions

@github-actions github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the Pi log parser (actions/setup/js/parse_pi_log.cjs) to handle the Pi CLI v3 streaming schema, which replaced the legacy flat schema in current Pi builds. Without this fix, step summaries for Pi-backed agent runs are empty — no conversation, no token stats.

Problem

The Pi CLI --mode json output schema changed between versions:

  • Legacy schema emits flat init / assistant / tool_use / tool_result / result events.
  • v3 streaming schema emits envelope events: session, turn_start, turn_end, tool_execution_start, tool_execution_end, agent_end.

The parser only handled the legacy schema, so v3 logs produced empty step summaries and no token usage stats.

Changes

actions/setup/js/parse_pi_log.cjs

  • Schema detectionisPiV3Schema(rawEntries): scans entries for v3 marker event types and returns a boolean.
  • v3 transformationtransformPiV3Entries(rawEntries): rebuilds canonical log entries from v3 events. Indexes tool_execution_end results by toolCallId, derives model from first turn_end.message.model, emits tool_use entries before their paired tool_result entries.
  • v3 statscomputePiV3Stats(rawEntries): sums usage.input and usage.output across all turn_end events.
  • Legacy statslegacyPiStats(rawEntries): extracts result.stats from the legacy schema.
  • Routing in parsePiLog(): detects schema once, dispatches to the correct transform and stats functions.
  • Exports: isPiV3Schema, transformPiV3Entries, computePiV3Stats added to module.exports.

actions/setup/js/parse_pi_log.test.cjs

New "Pi v3 streaming schema" test suite (6 tests): schema detection, end-to-end rendering, tool_use/tool_result ordering invariant, token aggregation across turns, OTEL enrichment on result entry, and error flag propagation.

.github/skills/agentic-workflows/SKILL.md

Added configure-agentic-engine.md to the lazy-load file list.

Compatibility

Legacy Pi logs are unchanged — isPiV3Schema returns false and the original legacyPiStats / transformPiEntries paths are used as before.

Generated by PR Description Updater for #45414 · 43.1 AIC · ⌖ 4.51 AIC · ⊞ 4.7K ·

The Pi CLI's `--print --mode json` output now emits a v3 streaming schema
(session / turn_start / turn_end / tool_execution_* / agent_end), but
parse_pi_log.cjs only understood the legacy flat schema
(init/assistant/tool_use/tool_result/result). As a result, every current
Pi-engine run rendered an empty step summary — no conversation, no tool
calls, no token/turn stats — and the OTEL result-entry enrichment was
skipped because no `result` event exists in v3.

Add additive v3 support: detect the schema and, for v3, render from the
finalized `turn_end` messages (text + toolCall) paired with
`tool_execution_end` results by id, emitting tool_use before tool_result
so the shared renderer resolves tool names and orders the conversation
correctly. Token stats sum per-turn output and take peak per-turn input.
The legacy path is unchanged. Adds v3 unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added automated-fix javascript Pull requests that update javascript code rendering labels Jul 14, 2026
@pelikhan pelikhan marked this pull request as ready for review July 14, 2026 09:00
Copilot AI review requested due to automatic review settings July 14, 2026 09:00
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (0 additions in default business logic directories: src/, lib/, pkg/, internal/, app/, core/, domain/, services/, api/).

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Pi v3 streaming-schema support while preserving legacy parsing.

Changes:

  • Detects and transforms v3 conversation/tool events.
  • Reconstructs token statistics and telemetry results.
  • Adds v3 parser tests.
Show a summary per file
File Description
actions/setup/js/parse_pi_log.cjs Implements v3 parsing and statistics.
actions/setup/js/parse_pi_log.test.cjs Adds v3 behavior tests.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 4
  • Review effort level: Medium

Comment thread actions/setup/js/parse_pi_log.cjs Outdated
type: "tool_result",
tool_use_id: id,
content: extractPiV3ResultText(res.result),
is_error: isPiV3ResultError(res.result),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the latest commit — is_error now evaluates res.isError || isPiV3ResultError(res.result), reading the top-level event flag first (matching the Pi driver's event.isError check at pi_agent_core_driver.cjs:348).

Comment thread actions/setup/js/parse_pi_log.test.cjs Outdated
it("marks failed tool results as errors", () => {
const entries = transformPiV3Entries([
{ type: "session", version: 3, id: "s" },
{ type: "tool_execution_end", toolCallId: "t1", result: { status: "error", content: [{ type: "text", text: "boom" }] } },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in the latest commit — the test event now carries isError: true at the top level (no status: "error" in result), matching the real v3 event shape from the Pi driver.

Comment thread actions/setup/js/parse_pi_log.cjs Outdated
Comment on lines +388 to +390
if (typeof usage.input === "number" && usage.input > maxInputTokens) {
maxInputTokens = usage.input;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — computePiV3Stats now sums input tokens across all turns (inputTokens += usage.input), matching the Pi driver's accumulation at pi_agent_core_driver.cjs:360. The "max" approach and its comment have been removed.

Comment thread actions/setup/js/parse_pi_log.test.cjs Outdated
expect(stats).not.toBeNull();
expect(stats.turns).toBe(2);
expect(stats.output_tokens).toBe(55); // 40 + 15
expect(stats.input_tokens).toBe(1200); // max(1000, 1200)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated — both stat assertions now expect 2,200 input tokens (1000 + 1200), and the result entry assertion reflects the same sum.

@github-actions

Copy link
Copy Markdown
Contributor Author

🧪 Test Quality Sentinel Report

Test Quality Score: 85/100 — Excellent

Analyzed 6 test(s): 6 design, 0 implementation, 0 violation(s).

📊 Metrics (6 tests)
Metric Value
Analyzed 6 (Go: 0, JS: 6)
✅ Design 6 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 3 (50%)
Duplicate clusters 0
Inflation No (98 test lines / 248 prod lines = 0.39:1)
🚨 Violations 0
Test File Classification Issues
detects the v3 schema and rejects the legacy schema parse_pi_log.test.cjs design_test / high_value
renders assistant text, tool calls, and tool results from a v3 stream parse_pi_log.test.cjs design_test / high_value
emits each tool_use before its paired tool_result in a v3 turn parse_pi_log.test.cjs design_test / high_value
computes v3 stats: turns counted, output summed, input peaked parse_pi_log.test.cjs design_test / high_value
includes a normalized v3 result entry for OTEL enrichment parse_pi_log.test.cjs design_test / high_value
marks failed tool results as errors parse_pi_log.test.cjs design_test / high_value

Verdict

Passed. 0% implementation tests (threshold: 30%). All 6 tests verify behavioral contracts for the new v3 streaming schema: schema detection, end-to-end rendering, tool ordering invariant, token stats computation, OTEL result normalization, and error propagation.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 33.3 AIC · ⌖ 9.36 AIC · ⊞ 6.8K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 85/100. 0% implementation tests (threshold: 30%). All 6 new tests are behavioral design tests covering the v3 streaming schema.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix is well-structured and correct. The dual-schema detection is robust, the tool_use-before-tool_result ordering is properly enforced, and input-token peak vs output-token sum logic matches the described Pi v3 behaviour. Legacy path is untouched and the new tests cover all key scenarios (detection, ordering, stats, error results, OTEL entry). No blocking issues found.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 17.1 AIC · ⌖ 5.12 AIC · ⊞ 4.8K

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — two low-severity findings, no blocking issues.

📋 Key Themes & Highlights

Key Themes

  • Coverage gap for legacy path: legacyPiStats is unexported and has no direct test; a field rename would produce silent zero-stats.
  • Misleading fallback stats: computePiV3Stats returns a zero-token object rather than null when turns are present but all lack usage data.

Positive Highlights

  • ✅ Clean schema-detection heuristic (isPiV3Schema) with a clear null-guard loop
  • ✅ Good ordering discipline: tool_use before tool_result within each turn
  • ✅ Well-documented token-accounting rationale (input = max, output = sum) in JSDoc
  • ✅ New test suite covers the v3 happy path, ordering, stats, error flags — solid baseline

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 40.9 AIC · ⌖ 4.58 AIC · ⊞ 6.6K
Comment /matt to run again

isPiV3Schema,
transformPiV3Entries,
computePiV3Stats,
};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] legacyPiStats is not exported, so it can only be exercised indirectly via parsePiLog — but no legacy-schema fixture exists in the new test suite. A rename of result.stats field names would silently produce zero stats with no failing test.

💡 Suggested fix

Add legacyPiStats to module.exports and add unit tests:

it('extracts stats from a legacy result event', () => {
  const stats = legacyPiStats([{ type: 'result', stats: { input_tokens: 100, output_tokens: 50, turns: 3, duration_ms: 1500 } }]);
  expect(stats).toEqual({ input_tokens: 100, output_tokens: 50, turns: 3, duration_ms: 1500 });
});
it('returns null when no legacy result event', () => {
  expect(legacyPiStats([{ type: 'init' }])).toBeNull();
});

@copilot please address this.

}
}

if (turns === 0 && !sawUsage) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] When turns > 0 but no usage is present (e.g. a future Pi build stops emitting per-turn usage), computePiV3Stats returns {input_tokens:0, output_tokens:0, turns:N} rather than null. The step summary would show a stats section with all-zero tokens, which is misleading rather than absent.

💡 Suggested fix

Change the guard to also return null when no usage data was observed:

if (!sawUsage) {
  return null; // no token data available — omit the stats block entirely
}

And add a test case:

it('returns null when turns exist but no usage fields', () => {
  expect(computePiV3Stats([{ type: 'turn_end', message: { content: [] } }])).toBeNull();
});

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES — 3 issues must be addressed before merge

The fix correctly identifies and solves the root cause (v3 streaming schema not handled). The logic for rendering turn_end messages, pairing tool calls with results, and preserving the legacy path is sound. However three issues in the new code need to be fixed:

Blocking issues

1. computePiV3Stats null-guard is wrong (line ~395): When turn_end events exist but none carry a usage field, the function returns a zero-filled stats object instead of null. OTEL token-usage metrics will receive turns with zero tokens rather than absent data — this is a silent data-quality regression.

2. isPiV3Schema triggers on a single v3 event (line ~217): A single stray envelope event (e.g. agent_start) in a legacy log forces the entire log through the v3 transformer, silently dropping all legacy assistant/tool_use/result content. Requiring at least one turn_end (the load-bearing event) alongside a v3 marker is a much stronger and safer heuristic.

3. duration_ms is hardcoded to 0 (line ~251): Every v3 run will silently report zero latency to OTEL. The session event already carries a timestamp; if agent_end does too, compute the wall-clock duration rather than hard-coding zero.

🔎 Code quality review by PR Code Quality Reviewer · 45.4 AIC · ⌖ 4.82 AIC · ⊞ 5.4K
Comment /review to run again

}

if (turns === 0 && !sawUsage) {
return null;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

computePiV3Stats returns misleading zero-token stats instead of null when turn_end events exist but carry no usage data.

💡 Detail and suggested fix

The null-guard at line ~395 is:

if (turns === 0 && !sawUsage) {
  return null;
}

If turn_end entries exist but carry no usage field (older v3 build, streaming error, truncated log), turns > 0 while sawUsage stays false. The function returns { input_tokens: 0, output_tokens: 0, turns: N, duration_ms: 0 } instead of null, so the caller emits a synthetic stats block with all-zero token counts into the OTEL enrichment path — downstream gh-aw.turns / token-usage metrics show turns with zero tokens rather than absent data.

Fix the guard:

if (!sawUsage) {
  return turns === 0 ? null : { input_tokens: 0, output_tokens: 0, turns, duration_ms: 0 };
}

Or simply return null when !sawUsage if downstream consumers prefer fully absent stats over partial data.

return true;
}
}
return false;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isPiV3Schema returns true on any single v3 event, forcing the entire log through the v3 path — a single stray v3 event in a legacy log silently drops the whole conversation.

💡 Detail and suggested fix

Any single matching event (including a bare {type:"agent_start"} injected by a wrapper script or log aggregator) causes isPiV3Schema to return true, after which transformPiV3Entries processes the log as v3 and ignores all legacy assistant / tool_use / result events. The resulting conversation will be empty or severely truncated with no error surfaced.

Two concrete failure scenarios:

  1. A legacy log that happens to include a session-management envelope at the top (e.g. from a launcher) returns true and renders nothing.
  2. A partial v3 log with both schema types (mid-migration Pi build) silently loses all legacy-schema content.

Consider requiring at least one turn_end (the load-bearing event for v3 content) before committing to the v3 path:

This is a stronger signal: v3 logs always finalize turns with turn_end, so requiring both a v3 marker and at least one turn_end eliminates false positives from stray envelope events.

}
}

// Initialization entry from the session event, with the model taken from the first

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

computePiV3Stats hardcodes duration_ms: 0, silently emitting incorrect latency data to OTEL consumers.

💡 Detail and suggested fix

The v3 stream includes timing data that makes duration_ms derivable. The session event has a timestamp field, and agent_end typically carries a timestamp too. Hardcoding 0 means every v3 run reports zero duration in OTEL spans and the step summary information section.

If the upstream agent_end event carries a timestamp, compute the wall-clock duration:

const sessionEvent = rawEntries.find(e => e.type === "session");
const agentEndEvent = rawEntries.find(e => e.type === "agent_end");
let duration_ms = 0;
if (sessionEvent && sessionEvent.timestamp && agentEndEvent && agentEndEvent.timestamp) {
  duration_ms = new Date(agentEndEvent.timestamp) - new Date(sessionEvent.timestamp);
  if (!isFinite(duration_ms) || duration_ms < 0) duration_ms = 0;
}

If agent_end does not carry a timestamp in the real schema, document that duration_ms is intentionally unavailable rather than silently emitting 0.

@github-actions

Copy link
Copy Markdown
Contributor Author

Sighthound Security Scan — Run 29320112653

126 total findings (113 outside testdata directories).

Triage Summary

Most findings are false positives for this project:

  • Unsafe Deserialization (54)yaml.Unmarshal across pkg/workflow/ and pkg/parser/. gh-aw is a YAML workflow compiler; YAML parsing is its primary function. Not exploitable via Go deserialization gadgets.
  • Command Injection (23) — All use exec.Command(cmd, arg1, arg2, ...) with structured args, not shell-constructed strings. Not shell-injectable.
  • Code Injection (17) — All in actions/setup/js/add_comment.test.cjs; eval in test harness, not production code.

Potentially Actionable Findings

Severity Type File Notes
High Path Traversal pkg/cli/download_workflow.go:105 os.WriteFile to a path derived from user input (path). Validate/canonicalize before writing.
High SSRF scripts/ensure-docs-slide-pdf.js:110 fetch(url) with externally derived URL. Build-script only, limited blast radius.
Medium Insecure postMessage docs/public/wasm/compiler-worker.js:38 Missing targetOrigin restriction. Specify explicit origin on postMessage calls.
Medium DOM-based XSS docs/src/scripts/responsive-tables.ts:11 Review whether any HTML is inserted unsanitized via innerHTML.
Medium Insecure Randomness (5) pkg/cli/pr_helpers.go:39 rand.Intn — likely used for non-security purposes.

Recommended Actions

  1. pkg/cli/download_workflow.go:105 — Use filepath.Clean and assert the resolved path stays within the expected directory.
  2. docs/public/wasm/compiler-worker.js — Pass an explicit targetOrigin to postMessage, or validate event.origin in the receiver.
  3. docs/src/scripts/responsive-tables.ts — Prefer textContent over innerHTML; sanitize any HTML before insertion.

Testdata findings (13) are intentional vulnerable code samples used to test gh-aw linters — not actionable.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by 🛡️ Sighthound Security Scan for #45414 · 25.2 AIC · ⌖ 7.82 AIC · ⊞ 4.1K ·

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

pr-sous-chef
@copilot please run the pr-finisher skill, address unresolved review comments, and rerun checks once the branch is up to date.

Unresolved review threads:

Run: https://github.com/github/gh-aw/actions/runs/29325358083

Generated by 👨‍🍳 PR Sous Chef · 5.27 AIC · ⌖ 7.39 AIC · ⊞ 5.1K ·
Comment /souschef to run again

Copilot AI and others added 2 commits July 14, 2026 10:38
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
…ob syntax

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Copilot AI requested a review from gh-aw-bot July 14, 2026 10:43
@pelikhan pelikhan merged commit 31a1099 into main Jul 14, 2026
15 of 16 checks passed
@pelikhan pelikhan deleted the fix-pi-parser-v3-schema-6ddf5857c4ec2fa8 branch July 14, 2026 11:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated-fix javascript Pull requests that update javascript code rendering

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants