feat: merge gh aw audit report into gh aw logs --format#24396
Conversation
… report subcommand - Add --format flag to `gh aw logs` with markdown/pretty values to generate cross-run security audit reports (executive summary, domain inventory, metrics trends, MCP server health, per-run breakdown) - Add --last flag as an alias for --count for compatibility - Remove `gh aw audit report` subcommand (audit_report_cross_run_command.go) - Remove unused maxAuditReportRuns constant from audit_cross_run.go - Update all tests that called DownloadWorkflowLogs directly to pass new format param - Replace TestNewAuditReportSubcommand* tests with TestNewLogsCommand_HasFormatFlag, TestLogsCommand_FormatPrecedence, TestLogsCommand_RepoParsingWithHost - Update docs: audit.md, glossary.md, cost-management.md, blog post Agent-Logs-Url: https://github.com/github/gh-aw/sessions/33720f43-ca5a-402b-a2de-032ffa8269fd Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/33720f43-ca5a-402b-a2de-032ffa8269fd Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR consolidates the cross-run security audit report into gh aw logs via a new --format flag, and removes the now-redundant gh aw audit report subcommand.
Changes:
- Added
--format markdown|pretty(and--lastalias for--count) togh aw logsto render cross-run reports from already-processed runs. - Removed the
audit reportsubcommand and related constants/wiring. - Updated unit tests and documentation to reference
gh aw logs --formatas the new entry point.
Show a summary per file
| File | Description |
|---|---|
| pkg/cli/logs_orchestrator.go | Adds format parameter and renders cross-run report output modes. |
| pkg/cli/logs_command.go | Adds --format and --last flags and passes format through to orchestrator. |
| pkg/cli/logs_json_stderr_order_test.go | Updates orchestrator callsites for new format parameter. |
| pkg/cli/logs_download_test.go | Updates orchestrator callsites for new format parameter. |
| pkg/cli/logs_ci_scenario_test.go | Updates orchestrator callsites for new format parameter. |
| pkg/cli/context_cancellation_test.go | Updates orchestrator callsites for new format parameter. |
| pkg/cli/audit.go | Removes registration of the deleted audit report subcommand. |
| pkg/cli/audit_report_cross_run_command.go | Removes the legacy cross-run report subcommand implementation. |
| pkg/cli/audit_cross_run.go | Removes the legacy maxAuditReportRuns constant (report generation remains). |
| pkg/cli/audit_cross_run_test.go | Replaces audit report command tests with logs --format flag/precedence tests. |
| docs/src/content/docs/reference/glossary.md | Updates glossary entry to point to gh aw logs --format. |
| docs/src/content/docs/reference/cost-management.md | Updates guidance to use gh aw logs --format ... for cross-run trends. |
| docs/src/content/docs/reference/audit.md | Updates reference docs from audit report to logs --format. |
| docs/src/content/docs/blog/2026-03-30-weekly-update.md | Updates blog content to reference gh aw logs --format. |
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/audit_cross_run_test.go:447
- This test asserts repo parsing for
[HOST/]owner/repoby splitting and taking the last two path segments, but the production logs path (downloadRunArtifactsConcurrent) currently usesstrings.SplitN(repoOverride, "/", 2), which will mis-parsehost/owner/repoas owner=host, repo=owner/repo. Consider extracting a shared repo parser used by logs/audit (including optional host) and updating the test to exercise that implementation.
// Apply the same repo parsing logic used in audit commands
parts := strings.Split(tt.repoFlag, "/")
if len(parts) < 2 {
assert.True(t, tt.wantErr, "Should expect error for: %s", tt.repoFlag)
return
}
ownerPart := parts[len(parts)-2]
repoPart := parts[len(parts)-1]
- Files reviewed: 14/14 changed files
- Comments generated: 4
| report := buildCrossRunAuditReport(inputs) | ||
| if jsonOutput { | ||
| return renderCrossRunReportJSON(report) | ||
| } |
There was a problem hiding this comment.
When --format is set and --json is also requested, the cross-run JSON path is only reached if at least one run is processed. If no runs match (or all are filtered out), the earlier len(processedRuns)==0 fast-return renders the metrics JSON instead, which contradicts the documented behavior that --json in format mode produces the cross-run report JSON. Consider handling the no-runs case for format mode explicitly (e.g., emit an empty CrossRunAuditReport JSON/markdown/pretty as appropriate).
| inputs := make([]crossRunInput, 0, len(processedRuns)) | ||
| for _, pr := range processedRuns { | ||
| inputs = append(inputs, crossRunInput{ | ||
| RunID: pr.Run.DatabaseID, | ||
| WorkflowName: pr.Run.WorkflowName, | ||
| Conclusion: pr.Run.Conclusion, | ||
| Duration: pr.Run.Duration, | ||
| FirewallAnalysis: pr.FirewallAnalysis, | ||
| Metrics: LogMetrics{ | ||
| TokenUsage: pr.Run.TokenUsage, | ||
| EstimatedCost: pr.Run.EstimatedCost, | ||
| Turns: pr.Run.Turns, | ||
| }, | ||
| MCPToolUsage: pr.MCPToolUsage, | ||
| MCPFailures: pr.MCPFailures, | ||
| ErrorCount: pr.Run.ErrorCount, | ||
| }) | ||
| } |
There was a problem hiding this comment.
The cross-run report inputs are built only from processedRuns, which excludes runs that were skipped due to missing/expired artifacts (ErrNoArtifacts) or other skip conditions. This means the cross-run report will silently omit those runs and undercount RunsWithoutData, unlike the previous audit report behavior that intentionally included skipped runs as HasData=false. To preserve report accuracy, consider including skipped runs in the aggregation inputs (with FirewallAnalysis=nil), or altering skip behavior when --format is active.
| inputs := make([]crossRunInput, 0, len(processedRuns)) | |
| for _, pr := range processedRuns { | |
| inputs = append(inputs, crossRunInput{ | |
| RunID: pr.Run.DatabaseID, | |
| WorkflowName: pr.Run.WorkflowName, | |
| Conclusion: pr.Run.Conclusion, | |
| Duration: pr.Run.Duration, | |
| FirewallAnalysis: pr.FirewallAnalysis, | |
| Metrics: LogMetrics{ | |
| TokenUsage: pr.Run.TokenUsage, | |
| EstimatedCost: pr.Run.EstimatedCost, | |
| Turns: pr.Run.Turns, | |
| }, | |
| MCPToolUsage: pr.MCPToolUsage, | |
| MCPFailures: pr.MCPFailures, | |
| ErrorCount: pr.Run.ErrorCount, | |
| }) | |
| } | |
| processedByRunID := make(map[int64]processedRun, len(processedRuns)) | |
| for _, pr := range processedRuns { | |
| processedByRunID[pr.Run.DatabaseID] = pr | |
| } | |
| // Build report inputs from the full set of runs, not only successfully processed ones, | |
| // so skipped runs are still counted as runs without data. | |
| inputs := make([]crossRunInput, 0, len(runs)) | |
| for _, run := range runs { | |
| input := crossRunInput{ | |
| RunID: run.DatabaseID, | |
| WorkflowName: run.WorkflowName, | |
| Conclusion: run.Conclusion, | |
| Duration: run.Duration, | |
| Metrics: LogMetrics{ | |
| TokenUsage: run.TokenUsage, | |
| EstimatedCost: run.EstimatedCost, | |
| Turns: run.Turns, | |
| }, | |
| ErrorCount: run.ErrorCount, | |
| } | |
| if pr, ok := processedByRunID[run.DatabaseID]; ok { | |
| input.FirewallAnalysis = pr.FirewallAnalysis | |
| input.MCPToolUsage = pr.MCPToolUsage | |
| input.MCPFailures = pr.MCPFailures | |
| } | |
| inputs = append(inputs, input) | |
| } |
| logsCmd.Flags().String("format", "", "Output format for cross-run audit report: markdown, pretty (generates security audit report instead of default metrics table)") | ||
| logsCmd.Flags().Int("last", 0, "Alias for --count: number of recent runs to analyze when generating a cross-run report") |
There was a problem hiding this comment.
--format accepts arbitrary strings; any unexpected value currently falls back to the default metrics output with no error, which can look like the flag was ignored. Consider validating --format (allowing only "", "markdown", "pretty") and returning a clear error for invalid values.
| ### Cross-Run Audit Report (`gh aw logs --format`) | ||
|
|
||
| A `gh aw audit` subcommand that aggregates firewall data across multiple workflow runs to produce a cross-run security report. The report includes an executive summary, domain inventory, and per-run breakdown. Designed for security reviews, compliance checks, and feeding debugging or optimization agents. Outputs markdown by default (suitable for `$GITHUB_STEP_SUMMARY`), or pretty/JSON format. See [CLI Reference](/gh-aw/setup/cli/#audit-report). | ||
| A feature of `gh aw logs` that aggregates firewall data across multiple workflow runs to produce a cross-run security report. The report includes an executive summary, domain inventory, and per-run breakdown. Designed for security reviews, compliance checks, and feeding debugging or optimization agents. Outputs markdown by default (suitable for `$GITHUB_STEP_SUMMARY`), or pretty/JSON format. See [CLI Reference](/gh-aw/setup/cli/#logs). |
There was a problem hiding this comment.
The glossary entry says the cross-run report "Outputs markdown by default", but gh aw logs only generates the cross-run report when --format is explicitly set (and the default --format is empty). Consider rewording to clarify that the report output is selected via --format markdown|pretty (and --json for JSON).
| A feature of `gh aw logs` that aggregates firewall data across multiple workflow runs to produce a cross-run security report. The report includes an executive summary, domain inventory, and per-run breakdown. Designed for security reviews, compliance checks, and feeding debugging or optimization agents. Outputs markdown by default (suitable for `$GITHUB_STEP_SUMMARY`), or pretty/JSON format. See [CLI Reference](/gh-aw/setup/cli/#logs). | |
| A feature of `gh aw logs` that aggregates firewall data across multiple workflow runs to produce a cross-run security report. The report includes an executive summary, domain inventory, and per-run breakdown. Designed for security reviews, compliance checks, and feeding debugging or optimization agents. Select report output with `--format markdown` (suitable for `$GITHUB_STEP_SUMMARY`) or `--format pretty`, and use `--json` for JSON output. See [CLI Reference](/gh-aw/setup/cli/#logs). |
gh aw audit reportwas a separate subcommand that duplicated the run-fetching and artifact-download pipeline already ingh aw logs. This PR consolidates the cross-run security report output intologsvia a--formatflag, then removes theaudit reportsubcommand.Changes
gh aw logs(logs_command.go,logs_orchestrator.go)--format markdown|pretty— when set, buildscrossRunInputentries from the already-collectedprocessedRunsand renders via the existingrenderCrossRunReport{Markdown,Pretty,JSON}functions instead of the default metrics table--lastas an alias for--count(drop-in compat for formeraudit report --lastusers)--jsoncontinues to work in format mode, producing cross-run report JSONRemoved
audit_report_cross_run_command.go—NewAuditReportSubcommand,RunAuditReport,RunAuditReportConfigall deletedcmd.AddCommand(NewAuditReportSubcommand())removed fromaudit.gomaxAuditReportRunsconstant removed fromaudit_cross_run.goTests (
audit_cross_run_test.go,context_cancellation_test.go,logs_*_test.go)TestNewAuditReportSubcommand*/TestRunAuditReportConfig*withTestNewLogsCommand_HasFormatFlag,TestLogsCommand_FormatPrecedence,TestLogsCommand_RepoParsingWithHostDownloadWorkflowLogscall sites to pass the newformatparameterDocs —
audit.md,glossary.md,cost-management.md, blog post updated to referencegh aw logs --formatinstead ofgh aw audit reportMigration