Skip to content

fix: recover USD cost from log files when events.jsonl has no cost data#28954

Closed
Copilot wants to merge 2 commits into
mainfrom
copilot/fix-executive-summary-cost
Closed

fix: recover USD cost from log files when events.jsonl has no cost data#28954
Copilot wants to merge 2 commits into
mainfrom
copilot/fix-executive-summary-cost

Conversation

Copilot AI commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

When events.jsonl became the primary metrics source, EstimatedCost stopped being populated — events.jsonl only carries an integer premium-request count, not a USD amount. This caused summary.total_cost = 0 in gh aw logs --json, propagating to $0 totals in copilot-token-audit reports.

Changes

  • pkg/cli/logs_metrics.go: After a successful events.jsonl parse (turns/tokens/tool calls), if EstimatedCost == 0, perform a targeted .log file walk solely to accumulate cost from Copilot API response JSON — the same extraction path that worked before events.jsonl took priority. Errors from that walk are now logged rather than silently discarded.

  • pkg/cli/copilot_events_jsonl_test.go: Regression test — recovers_cost_from_log_files_even_when_events.jsonl_is_present — verifies that a directory with a valid events.jsonl (no cost) and a .log containing "total_cost_usd": 0.042 produces EstimatedCost > 0 while preserving events.jsonl-sourced turns/tokens.

Copilot AI and others added 2 commits April 28, 2026 11:50
When events.jsonl is used as the primary metrics source, turns/tokens/tool
calls are accurate but EstimatedCost is never set (events.jsonl only carries
integer premium-request counts, not USD amounts). This caused summary.total_cost
= 0 in `gh aw logs --json`, which in turn made the copilot-token-audit report
show "0 total cost" in its executive summary.

Fix: after a successful events.jsonl parse, if EstimatedCost is still 0, walk
the .log files solely to accumulate cost from Copilot API response JSON (the
same source the log-file walk has always used). Turns, tokens, and tool calls
continue to come from events.jsonl, which is more accurate.

Add a regression test that asserts cost is recovered from a log file containing
`total_cost_usd` even when events.jsonl is present and provides turns/tokens.

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/7e6d90c6-7be8-4941-b0d1-16f2c3852546

Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com>
@pelikhan pelikhan marked this pull request as ready for review April 28, 2026 15:28
Copilot AI review requested due to automatic review settings April 28, 2026 15:28
@github-actions github-actions Bot mentioned this pull request Apr 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 100/100

Excellent test quality

Metric Value
New/modified tests analyzed 1
✅ Design tests (behavioral contracts) 1 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 1 (100%)
Duplicate test clusters 0
Test inflation detected No (0.88:1 ratio)
🚨 Coding-guideline violations None

Test Classification Details

Test File Classification Issues Detected
recovers cost from log files even when events.jsonl is present pkg/cli/copilot_events_jsonl_test.go ✅ Design None

Language Support

Tests analyzed:


Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%).

The new subtest verifies a clear behavioral contract: when events.jsonl is present but contains no USD cost data, extractLogMetrics should recover the cost from .log files while still sourcing turns and token counts from events.jsonl. All three assertions check observable outputs with descriptive messages. No coding-guideline violations detected.

📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

References:

🧪 Test quality analysis by Test Quality Sentinel · ● 323.1K ·

@github-actions github-actions Bot 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.

✅ Test Quality Sentinel: 100/100. Test quality is acceptable — 0% of new tests are implementation tests (threshold: 30%).

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

This PR restores non-zero USD cost reporting when events.jsonl is present (and used for turns/tokens) but does not include cost fields, by recovering EstimatedCost from Copilot API JSON embedded in .log files.

Changes:

  • Add a cost-only log-file walk that runs when events.jsonl parses successfully but EstimatedCost is still 0.
  • Log failures from the cost recovery walk (instead of silently discarding them).
  • Add a regression test ensuring cost is recovered from .log files while turns/tokens remain sourced from events.jsonl.
Show a summary per file
File Description
pkg/cli/logs_metrics.go Adds a cost-recovery log walk when events.jsonl yields no cost.
pkg/cli/copilot_events_jsonl_test.go Adds coverage for recovering cost from .log files even when events.jsonl is present.

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/logs_metrics.go:189

  • fileErr from parseLogFileWithEngine is only surfaced when verbose is true; when verbose is false it is silently ignored, despite the PR intent to log cost-walk errors. Also, cost is still added even when fileErr != nil (using whatever metrics value was returned). Consider always logging parse/read failures to logsMetricsLog (and optionally stderr when verbose) and skipping accumulation for that file when an error occurs.
				fileMetrics, fileErr := parseLogFileWithEngine(path, detectedEngine, isGitHubCopilotCodingAgent, verbose)
				if fileErr != nil && verbose {
					fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to parse log file %s for cost: %v", path, fileErr)))
					return nil
				}
				metrics.EstimatedCost += fileMetrics.EstimatedCost
			}
  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment thread pkg/cli/logs_metrics.go
Comment on lines +178 to +190
fileName := strings.ToLower(info.Name())
if (strings.HasSuffix(fileName, ".log") ||
(strings.HasSuffix(fileName, ".txt") && strings.Contains(fileName, "log"))) &&
!strings.Contains(fileName, "aw_output") &&
fileName != constants.AgentOutputFilename {
fileMetrics, fileErr := parseLogFileWithEngine(path, detectedEngine, isGitHubCopilotCodingAgent, verbose)
if fileErr != nil && verbose {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to parse log file %s for cost: %v", path, fileErr)))
return nil
}
metrics.EstimatedCost += fileMetrics.EstimatedCost
}
return nil

Copilot AI Apr 28, 2026

Copy link

Choose a reason for hiding this comment

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

walkLogFilesForCost duplicates the file filtering and traversal logic used in the main log walk below. To reduce the chance these two paths drift over time (e.g., different skip rules), consider extracting a shared helper that iterates relevant log files and lets callers decide what to aggregate (full metrics vs cost-only).

Copilot uses AI. Check for mistakes.
Comment thread pkg/cli/logs_metrics.go
walkLogFilesForCost := func() {
if walkErr := filepath.Walk(logDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err

Copilot AI Apr 28, 2026

Copy link

Choose a reason for hiding this comment

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

In the cost-recovery walk, returning the incoming err from the filepath.Walk callback will abort the entire walk on the first filesystem traversal error (e.g., unreadable file/dir), which can prevent recovering cost from remaining log files. Since this path is intended to be best-effort, consider logging the traversal error and returning nil to continue walking (or SkipDir where appropriate).

This issue also appears on line 183 of the same file.

Suggested change
return err
logsMetricsLog.Printf("Skipping path during cost recovery walk %s: %v", path, err)
if verbose {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Skipping path during cost recovery walk %s: %v", path, err)))
}
if info != nil && info.IsDir() {
return filepath.SkipDir
}
return nil

Copilot uses AI. Check for mistakes.
@mnkiefer mnkiefer closed this Apr 28, 2026
@github-actions github-actions Bot deleted the copilot/fix-executive-summary-cost branch May 6, 2026 03:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants