fix: handle null token_usage in optimizer jq aggregations#24400
Conversation
When runs have zero token usage, the field is omitted from JSON (omitempty), causing jq to see null. The expression `map(.token_usage) | add` returns null when all values are null, and `null / length` fails with: null (null) and number (1) cannot be divided Fix: - Use `// 0` fallback: `map(.token_usage // 0) | add` - Use actual estimated_cost field instead of hardcoded 0 - Add schema comments documenting RunData fields and nullability Fixes failed run: github/gh-aw/actions/runs/23965804699 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Fixes jq aggregation failures in the token optimizer workflows when token_usage is omitted (JSON omitempty → jq null), and improves cost aggregation.
Changes:
- Update jq aggregations to null-coalesce
token_usageandestimated_costwith// 0to prevent division errors. - Compute
total_costfrom run data instead of hardcoding0. - Regenerate the corresponding
.lock.ymlworkflow outputs.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/copilot-token-optimizer.md |
Makes jq aggregations resilient to missing token_usage/estimated_cost fields; adds inline schema notes. |
.github/workflows/copilot-token-optimizer.lock.yml |
Recompiled lockfile reflecting the updated workflow script content. |
.github/workflows/claude-token-optimizer.md |
Same jq null-coalescing + cost aggregation improvements as the Copilot workflow. |
.github/workflows/claude-token-optimizer.lock.yml |
Recompiled lockfile reflecting the updated workflow script content. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (4)
.github/workflows/copilot-token-optimizer.md:126
- The jq filter computes the same total token sum twice (for
total_tokensand again insideavg_tokens). Consider binding the sum to a local variable within themap(...)expression and reusing it; this reduces duplication and makes the aggregation easier to maintain.
map({
workflow: .[0].workflow_name,
total_tokens: (map(.token_usage // 0) | add),
total_cost: (map(.estimated_cost // 0) | add),
run_count: length,
avg_tokens: ((map(.token_usage // 0) | add) / length),
run_ids: map(.database_id),
.github/workflows/copilot-token-optimizer.md:115
- The schema comment “omitempty → null when 0” is a bit imprecise:
omitemptycauses the field to be omitted when zero, and jq sees a missing field asnull. Consider rewording to reflect “omitted when 0 → jq yields null when missing” to avoid implying the JSON explicitly containsnull.
# Schema: gh aw logs --json → LogsData.runs[] (RunData from pkg/cli/logs_report.go)
# .workflow_name (string), .token_usage (int, omitempty → null when 0),
# .estimated_cost (float, omitempty), .database_id (int64), .created_at (time), .url (string)
.github/workflows/claude-token-optimizer.md:126
- The jq filter computes the same total token sum twice (for
total_tokensand again insideavg_tokens). Consider binding the sum to a local variable within themap(...)expression and reusing it; this reduces duplication and makes the aggregation easier to maintain.
map({
workflow: .[0].workflow_name,
total_tokens: (map(.token_usage // 0) | add),
total_cost: (map(.estimated_cost // 0) | add),
run_count: length,
avg_tokens: ((map(.token_usage // 0) | add) / length),
run_ids: map(.database_id),
.github/workflows/claude-token-optimizer.md:115
- The schema comment “omitempty → null when 0” is a bit imprecise:
omitemptycauses the field to be omitted when zero, and jq sees a missing field asnull. Consider rewording to reflect “omitted when 0 → jq yields null when missing” to avoid implying the JSON explicitly containsnull.
# Schema: gh aw logs --json → LogsData.runs[] (RunData from pkg/cli/logs_report.go)
# .workflow_name (string), .token_usage (int, omitempty → null when 0),
# .estimated_cost (float, omitempty), .database_id (int64), .created_at (time), .url (string)
- Files reviewed: 4/4 changed files
- Comments generated: 2
| # Find the most expensive workflow (by total tokens across all its runs) | ||
| # Schema: gh aw logs --json → LogsData.runs[] (RunData from pkg/cli/logs_report.go) | ||
| # .workflow_name (string), .token_usage (int, omitempty → null when 0), | ||
| # .estimated_cost (float, omitempty), .database_id (int64), .created_at (time), .url (string) |
There was a problem hiding this comment.
PR description mentions adding schema comments in pkg/cli/logs_report.go, but this change appears to add schema comments here in the workflow instead. If the Go schema documentation was also intended to change, please include it in the PR; otherwise consider updating the PR description/files-changed list to match what’s actually modified.
This issue also appears in the following locations of the same file:
- line 113
- line 120
| # Find the most expensive workflow (by total tokens across all its runs) | ||
| # Schema: gh aw logs --json → LogsData.runs[] (RunData from pkg/cli/logs_report.go) | ||
| # .workflow_name (string), .token_usage (int, omitempty → null when 0), | ||
| # .estimated_cost (float, omitempty), .database_id (int64), .created_at (time), .url (string) |
There was a problem hiding this comment.
PR description mentions adding schema comments in pkg/cli/logs_report.go, but this change appears to add schema comments here in the workflow instead. If the Go schema documentation was also intended to change, please include it in the PR; otherwise consider updating the PR description/files-changed list to match what’s actually modified.
This issue also appears in the following locations of the same file:
- line 113
- line 120
lpcox
left a comment
There was a problem hiding this comment.
Addressed the review feedback — the PR description was misleading. The schema comments are intentionally inline in the workflow .md files (next to the jq expressions that consume the data), not in pkg/cli/logs_report.go. The Go struct is unchanged; the inline comments reference it as the source of truth for field names and nullability. Updated the PR description to clarify this.
Problem
The Copilot Token Optimizer failed in run 23965804699 with:
The
token_usagefield inRunDatausesomitempty, so when a run has zero token usage the field is omitted from JSON. In jq, accessing an omitted field yieldsnull. When all runs in a workflow group have null token usage:map(.token_usage) | add→nullnull / length→ division errorFix
// 0null coalescing:map(.token_usage // 0) | addtotal_cost: 0with actual field:map(.estimated_cost // 0) | addRunDatafields and their nullability (source of truth:pkg/cli/logs_report.go)Files Changed
.github/workflows/copilot-token-optimizer.md— null-safe jq, schema comments, estimated_cost field.github/workflows/claude-token-optimizer.md— same changes.lock.ymlfiles (recompiled)No changes to
pkg/cli/logs_report.go— the Go struct is unchanged; comments in the workflows reference it as the schema source.Testing