Skip to content

Add experimental sessions download with legacy run reconstruction - #65263

Merged
pelikhan merged 4 commits into
mainfrom
pelikhan-sessions-download
Oct 3, 2026
Merged

pelikhan merged 4 commits into
mainfrom
pelikhan-sessions-download

Conversation

@pelikhan

@pelikhan pelikhan commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator

Workflow sessions should be easy to export without generating a full audit report, including runs produced before gh-aw published unified session files. This adds experimental gh aw sessions download <run-id-or-url> with the same engine parsers used by audit and workflow summaries.

Approach

  • Prefer usage/aw_session.jsonl and return published JSONL byte-for-byte. If it is missing, reconstruct the current unified format from native agent events or raw engine logs, retaining available MCP, firewall, usage, and grader evidence.
  • Support older artifact names and layouts, including agent-artifacts, standalone agent logs, legacy metadata, workflow-call prefixes, and tmp/gh-aw nesting. Neither aw_session.jsonl nor agent-session.jsonl is required for historical runs.
  • Default to clean JSONL on stdout; provide --format markdown, --output/-o, and --repo/-r. Markdown uses the shared privacy-preserving, bounded renderer. Node.js is required for reconstruction and Markdown, but not for published JSONL downloads.
  • Package the current runtime parser sources for use outside a source checkout, replacing audit's obsolete embedded-script wrapper. Validate session headers and records, surface failures, and write output files atomically.
  • Mark the command experimental in help and documentation and add a minor changeset.

Reconstruction uses only downloaded evidence and reports missing components rather than inventing them. It does not download all downstream artifacts.

Validation

Passed:

  • Focused Go tests covering download precedence, output routing, both formats, six engine parsers, legacy layouts, invalid inputs, and audit integration:
    go test ./pkg/cli ./actions/setup ./cmd/gh-aw -run 'Test(SessionsDownload|SessionArtifactName|ValidateSessionJSONL|SessionParser|ParseAgentLog|Root|Help)' -count=1
  • JavaScript typecheck and 56 tests across the CLI adapter, unified collector, and unified renderer.
  • make build, make fmt, make lint-cjs, and standard Go lint for all changed packages.
  • Published JSONL from run 37083076742 matched its downloaded artifact exactly, and stdout piped cleanly into a JSONL consumer.

Historical samples below had no unified session file and successfully exported both JSONL and Markdown:

Engine Run date Run Reconstructed records
Claude 2026-09-11 34562419664 1,996
Codex 2026-09-17 35165572106 33
Copilot 2026-09-21 35647911532 534

The impacted test groups in make agent-report-progress passed. The combined repository gate remains blocked by seven pre-existing custom slice-index lint findings in unchanged code within cmd/gh-aw/main.go; these were left outside this feature's scope.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@pelikhan
pelikhan marked this pull request as ready for review October 3, 2026 06:05
Copilot AI balanced review requested due to automatic review settings October 3, 2026 06:05
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

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

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

✅ Test Quality Sentinel completed test quality analysis.

Test Quality Sentinel skipped because pre-fetch PR data was unavailable: unable to fetch test file diff

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

✅ Design Decision Gate 🏗️ completed the design decision gate check. See the comment below for the result and any generated ADR draft.

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

✅ Ponytail Reviewer completed successfully!

Lean already. Ship.

Generated by Ponytail Reviewer for #65263

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

✅ PR Code Quality Reviewer completed the code quality review.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (>100 new lines) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/65263-export-unified-session-downloads.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI couldn't infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-65263: Export Unified Session Downloads

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

🔎 Evidence used
  • /tmp/gh-aw/agent/adr-prefetch-summary.json reported 822 new lines in default business logic directories, so ADR enforcement is required.
  • /tmp/gh-aw/agent/pr.json described a new experimental gh aw sessions download command with legacy run reconstruction.
  • No ADR link was present in the PR body, and no matching ADR file for PR 65263 existed on the branch before the draft was generated.
❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 0042-use-postgresql.md for PR #42).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · pi · gpt54 · 40.9 AIC · ⌖ 8.11 AIC · ⊞ 10.9K · ◷
Comment /review to run again

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Comment Memory

reviewed_at: 2026-10-03T06:08:05Z
review_event: REQUEST_CHANGES
top_themes:
- behavior-defined engine parser regression in audit/session parsing
- legacy aw-info artifact matching ambiguity
files_reviewed:
- actions/setup/js/session_cli.cjs
- actions/setup/js/unified_session.cjs
- actions/setup/session_parsers.go
- cmd/gh-aw/main.go
- docs/src/content/docs/setup/cli.md
- pkg/cli/logs_parsing_javascript.go
- pkg/cli/session_parser.go
- pkg/cli/session_parser_test.go
- pkg/cli/sessions_command.go
- pkg/cli/sessions_download.go
- pkg/cli/sessions_download_test.go
comment_count: 2

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt54 · 81.5 AIC · ⌖ 7.35 AIC · ⊞ 20.2K · ◷
Comment /review to run again

@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.

Request changes

This adds useful session export plumbing, but two correctness regressions make it unsafe to merge as-is: audit no longer honors behavior-defined engine log parsers, and the legacy metadata matcher can misclassify *-aw-info as *-info.

Blocking themes
  • Behavior-defined engines now fall back to the generic custom parser during audit/session parsing, which can drop structured findings from existing workflows.
  • The artifact-name matcher is too broad for info, so mixed legacy/current metadata layouts can resolve ambiguously or fail even though this command is meant to support them.

🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt54 · 81.5 AIC · ⌖ 7.35 AIC · ⊞ 20.2K
Comment /review to run again

Comment thread pkg/cli/logs_parsing_javascript.go Outdated
cmd := exec.Command("node", "parser.js")
cmd.Dir = tempDir
output, err := cmd.CombinedOutput()
output, err := runSessionParser(context.Background(), "agent-markdown", agentLogPath, engine.GetID())

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.

This refactor drops behavior-defined engines’ custom log parsers, so gh aw audit can silently lose structured findings for any workflow that relies on a generated parser.

💡 Why this blocks merge

Before this change, audit keyed off engine.GetLogParserScriptId(), which is how behavior-defined engines surface their generated <engine>_log_parser.cjs parser. The new path passes only engine.GetID() into runSessionParser(), and session_cli.cjs only knows the built-in engine IDs; anything else falls back to parse_custom_log.cjs.

That means a custom engine with a real parser now degrades from its purpose-built parser to the generic heuristic parser, which can drop log entries, MCP failures, and max-turn detection from audit output even though the run itself produced the richer parser.

Please preserve the parser-script path for behavior-defined engines here — for example by materializing the engine-specific parser alongside the bundled runtime sources, or by teaching the session CLI to dispatch on GetLogParserScriptId() instead of only GetID().

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 acf1182: audit now materializes and invokes a behavior-defined engine’s generated parser source, preserving its structured entries and surfacing MCP failures and max-turn detection. Added regression coverage.

}
var matches []string
for _, name := range names {
if strings.HasSuffix(name, "-"+base) || (legacy != "" && strings.HasSuffix(name, "-"+legacy)) {

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.

The -<base> suffix match is too broad for info: it treats *-aw-info as an info artifact, which breaks the legacy/current metadata fallback this command is trying to support.

💡 Why this blocks merge

sessionEngine() looks for info before aw-info, but sessionArtifactName() matches any artifact whose name ends with -info. A workflow-call artifact like caller-aw-info therefore matches the generic info branch first, and a run that contains both caller-info and caller-aw-info will now surface as "multiple info artifacts" even though those are two different compatibility buckets.

That is exactly the mixed historical layout this feature claims to support, so the current matching can reject valid legacy runs or pick the wrong metadata source. The fix should make the workflow-call prefix match exact for the whole artifact name, or special-case info so aw-info and aw_info are resolved only by the legacy branch.

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 acf1182: workflow-call *-aw-info and *-aw_info names are excluded from generic info matching and handled by the legacy metadata bucket. Added a mixed-layout regression test.

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.

Copilot review overview

🟡 Changes recommended

File output currently gives sensitive full-session JSONL world-readable permissions.

Review effort: Balanced
Findings: 1 High severity

Open (1)
What changed in this PR

Adds experimental session export with legacy run reconstruction and shared audit/session parsers.

Changes:

  • Adds JSONL/Markdown session downloads with artifact fallback and validation.
  • Embeds shared Node.js parsers and reuses them for audit rendering.
  • Adds comprehensive tests, documentation, CLI wiring, and a changeset.
File Description
.changeset/​minor-sessions-download.md Records the minor feature addition.
actions/​setup/​js/​session_cli.cjs Implements reconstruction and rendering adapter.
actions/​setup/​js/​session_cli.test.cjs Tests adapter behavior and validation.
actions/​setup/​js/​unified_session.cjs Corrects the collector return type.
actions/​setup/​session_parsers.go Embeds runtime parser sources.
cmd/​gh-aw/​main.go Registers the sessions command.
docs/​src/​content/​docs/​setup/​cli.md Documents session downloads.
pkg/​cli/​logs_parsing_javascript.go Reuses shared session parsing for audits.
pkg/​cli/​session_parser.go Executes embedded parsers through Node.js.
pkg/​cli/​session_parser_test.go Covers engines and audit integration.
pkg/​cli/​sessions_command.go Defines command flags and output routing.
pkg/​cli/​sessions_download.go Downloads, reconstructs, and validates sessions.
pkg/​cli/​sessions_download_test.go Covers artifacts, formats, legacy layouts, and errors.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread pkg/cli/sessions_command.go Outdated
}
output, _ := cmd.Flags().GetString("output")
if output != "" {
if err := writeFileAtomically(output, content); err != nil {

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 acf1182: session output now uses a dedicated atomic writer with owner-only permissions. It applies 0600 for new/public destinations and intersects existing permissions with 0600 so restrictive modes are not widened; permission tests cover these cases.

@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.

Skills-Based Review 🧠

Applied /tdd and /codebase-design (via pr-triage's recommendation) to the new gh aw sessions download command and its reconstruction pipeline.

📋 Key Themes & Highlights

Key Themes

  • Untested fallback path: sessionEngine can return an empty engine ID when no metadata is found anywhere, relying entirely on the unified collector's source detection. No test exercises this branch directly.
  • Duplicated repo-flag parsing: sessions_command.go re-implements owner/repo splitting with a different helper (repoutil) than audit_command.go's applyAuditRepoFlag, with no comment explaining the divergence.

Positive Highlights

  • ✅ Excellent test coverage overall: 367 lines covering precedence (usage → agent reconstruction), six engine parsers, legacy artifact layouts, output routing, and error paths, all verified passing locally.
  • ✅ Clean extraction of session_cli.cjs/session_parsers.go to share runtime parsers between audit and the new command, removing a large chunk of fragile inline Node.js bootstrap code from logs_parsing_javascript.go.
  • ✅ validateSessionJSONL is a solid, well-tested safety net against malformed or unsupported session files before they reach the renderer.
  • ✅ Verified locally: go build ./... succeeds and the focused test suite (TestSessionsDownload*, TestSessionArtifactName, TestValidateSessionJSONL, TestSessionParser*, TestParseAgentLog*) passes.

No blocking issues found; the two inline notes are suggestions for follow-up test coverage and code-path clarity, not correctness bugs.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet50 · 102.5 AIC · ⌖ 13.5 AIC · ⊞ 10K
Comment /matt to run again

}
// The collector can read canonical events without engine metadata and can
// recognize Pi's dedicated stream. Other logs use its custom-engine parser.
sessionsDownloadLog.Print("No engine metadata available; using the unified collector's source detection")

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.

[/tdd] The silent fallback path (no engine metadata found, returns "") isn't exercised by any test in sessions_download_test.go — every test case supplies an aw_info.json with an engine_id. This is the exact path a truly legacy/metadata-less run would hit.

💡 Suggested test

Add a case to TestSessionsDownloadReconstructs (or a new focused test) that downloads only an agent artifact with no info/aw-info/activation artifact and no aw_info.json, then asserts the reconstruction still succeeds via the collector's source detection (e.g. for a Pi-stream or custom-engine log).

@copilot please address this.

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 acf1182: added an agent-artifact-only Pi-stream reconstruction test with no aw_info.json or metadata artifact, verifying collector source detection succeeds.

}
repoFlag, _ := cmd.Flags().GetString("repo")
if repoFlag != "" && components.Owner == "" {
ownerRepo, host := repoutil.NormalizeRepoForAPI(repoFlag)

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.

[/codebase-design] This re-implements audit_command.go's applyAuditRepoFlag repo-slug parsing inline instead of reusing it, and the behavior differs subtly: applyAuditRepoFlag validates owner/repo with strings.SplitN and rejects malformed input with a clear error, while this path uses repoutil.NormalizeRepoForAPI + SplitRepoSlug, a different parser with different error semantics.

💡 Suggestion

Either extract a single shared helper used by both audit and sessions, or add a short comment explaining why sessions intentionally uses the repoutil parser (e.g. because it must also accept a host prefix). As written, a reader has no way to tell if the divergence is intentional, which hurts navigability per /codebase-design.

@copilot please address this.

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.

Clarified in acf1182: sessions intentionally uses NormalizeRepoForAPI because its --repo flag supports [HOST/]owner/repo for GHES/Proxima; the audit helper only parses plain owner/repo.

@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.

Impeccable review (mode: critique + audit, new CLI command/feature)

Reviewed gh aw sessions download (new experimental command), its shared session_parser.go adapter, the embedded JS runtime bundle in actions/setup/session_parsers.go, and the pkg/cli/logs_parsing_javascript.go refactor that now reuses the same parser.

Findings: none blocking.

  • Command/flag design is consistent with existing audit/logs conventions (--repo, --output/-o, --format with completion), and sessions/download are both clearly marked "Experimental" in help text (verified via TestSessionsDownloadExperimentalHelp).
  • Artifact precedence (usage/aw_session.jsonl → agent artifact → legacy layouts) mirrors the legacy-layout handling already used in firewall_policy.go/logs_download_flatten.go (tmp/gh-aw/ prefix stripping), so it's not introducing a new ad hoc pattern.
  • validateSessionJSONL correctly enforces the leading session.format header, rejects duplicate headers, and validates namespaced type/object data, with solid table-driven tests (TestValidateSessionJSONL) covering malformed/legacy/array/null cases.
  • sessionArtifactName ambiguity handling (erroring on multiple suffix matches instead of silently combining different called workflows' sessions) is a good safety choice, and is tested (TestSessionArtifactName).
  • runSessionParser propagates stderr diagnostics and wraps Node.js-not-found errors clearly; writeFileAtomically/temp-dir cleanup patterns reused correctly.
  • go build, gofmt, and the full targeted Go test suite (TestSessionsDownload*, TestSessionArtifactName, TestValidateSessionJSONL, TestSessionParser*) pass locally with Node.js available.
  • JS-side session_cli.test.cjs couldn't be executed in this sandbox (vitest install blocked by a network/TLS restriction unrelated to the PR), but the test file itself is well-structured (round-trips reconstruction and Markdown rendering, verifies privacy redaction of prompts/tool args, and checks invalid-input error messages) and the PR description states 56 JS tests already passed in CI.

No actionable inline comments — the change is incremental, reuses established helpers (buildRepoFlag, flattenArtifactTree, parseAwInfo, resolveAuditHostname), and ships solid test coverage for both the happy paths and error paths.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · copilot · sonnet50 · 127.8 AIC · ⌖ 13.2 AIC · ⊞ 8.1K

@pelikhan

pelikhan commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator Author

@copilo run pr-finisher skill

@pelikhan

pelikhan commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator Author

@copilot run pr-finisher skill

@pelikhan

pelikhan commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator Author

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

@copilot fix https://github.com/github/gh-aw/actions/runs/37102279383/job/111202560785

Fixed in 6ef9f7d: the Codex workflow test now permits an unset model (Codex's native default) and the intentional copilot/auto inference canary, while still failing unrelated explicit models. The focused test and full pkg/workflow suite pass.

@pelikhan
pelikhan merged commit a9771f7 into main Oct 3, 2026
@pelikhan
pelikhan deleted the pelikhan-sessions-download branch October 3, 2026 13:27
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.91.0

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.

3 participants