Skip to content

feat(logs): add --after flag to clean up cached run folders older than a date delta#29037

Merged
pelikhan merged 4 commits intomainfrom
copilot/add-date-time-delta-argument-to-logs
Apr 29, 2026
Merged

feat(logs): add --after flag to clean up cached run folders older than a date delta#29037
pelikhan merged 4 commits intomainfrom
copilot/add-date-time-delta-argument-to-logs

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 29, 2026

Summary

Adds a new --after flag to gh aw logs that deletes cached run folders in the output directory whose run creation date is older than the specified cutoff. This allows maintaining a rolling window of cached logs on a shared storage location.

Usage

# Delete cached run folders older than 1 week, then download the latest 10 runs
gh aw logs --after -1w

# Delete cached run folders older than 30 days
gh aw logs --after -30d

# Delete cached run folders from before a specific date
gh aw logs --after 2024-01-01

# Combine with other flags: clean up, then download specific workflow
gh aw logs my-workflow --after -1mo -c 20

The flag accepts the same date/time delta formats as --start-date and --end-date (e.g. -1d, -1w, -1mo) as well as absolute dates (YYYY-MM-DD).

How it works

  • Only directories matching the run-{ID} naming pattern inside the output directory are considered.
  • The run's creation timestamp is read from run_summary.json inside each folder.
  • If run_summary.json is absent (e.g. incomplete download), the directory's modification time is used as a fallback.
  • Cleanup runs before the download step so disk space is freed first.
  • Cleanup failures are non-fatal (logged as warnings); the download proceeds regardless.

Changes

File What changed
pkg/cli/logs_cache.go New parseCleanupCutoff helper and cleanupOldRunFolders function
pkg/cli/logs_command.go New --after flag definition, examples in help text, pass-through to orchestrator
pkg/cli/logs_orchestrator.go Updated DownloadWorkflowLogs signature; calls cleanupOldRunFolders when --after is set
pkg/cli/logs_cache_test.go New unit tests for cleanupOldRunFolders (7 table-driven cases + verbose test)
pkg/cli/logs_command_test.go Assertion that the --after flag is registered
Several *_test.go files Updated call sites to pass the new after parameter

Copilot AI and others added 2 commits April 29, 2026 02:53
…n a date delta

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/e0abd69b-b722-427e-a51a-de1fc42c8f62

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

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/e0abd69b-b722-427e-a51a-de1fc42c8f62

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI requested a review from pelikhan April 29, 2026 02:57
@pelikhan pelikhan marked this pull request as ready for review April 29, 2026 04:01
Copilot AI review requested due to automatic review settings April 29, 2026 04:01
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new --after flag to gh aw logs to clean up cached run-* folders older than a user-provided cutoff before downloading logs, enabling a rolling cache window.

Changes:

  • Introduce parseCleanupCutoff and cleanupOldRunFolders cache-maintenance helpers.
  • Wire --after through the logs command into the orchestrator to run cleanup pre-download.
  • Add/update unit tests to cover the new behavior and update call sites for the new parameter.
Show a summary per file
File Description
pkg/cli/logs_cache.go Adds cutoff parsing and cache folder cleanup logic.
pkg/cli/logs_orchestrator.go Adds after parameter and invokes cache cleanup before downloads.
pkg/cli/logs_command.go Registers --after flag, updates help examples, passes flag to orchestrator.
pkg/cli/logs_cache_test.go New unit tests for cleanupOldRunFolders.
pkg/cli/logs_command_test.go Verifies the after flag is registered.
pkg/cli/logs_json_stderr_order_test.go Updates orchestrator call sites with the new after argument.
pkg/cli/logs_download_test.go Updates orchestrator call sites with the new after argument.
pkg/cli/logs_ci_scenario_test.go Updates orchestrator call sites with the new after argument.
pkg/cli/context_cancellation_test.go Updates orchestrator call sites with the new after argument.

Copilot's findings

Tip

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

  • Files reviewed: 9/9 changed files
  • Comments generated: 3

Comment on lines +62 to +66
// Clean up cached run folders older than the --after cutoff, if specified.
if after != "" {
cutoff, parseErr := parseCleanupCutoff(after)
if parseErr != nil {
return parseErr
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

--after cleanup runs before the initial context-cancellation check, so a cancelled context can still trigger potentially expensive directory scanning/deletion. To preserve the existing “check context cancellation at the start” behavior, consider moving the cancellation check above the cleanup block, or pass ctx into cleanupOldRunFolders and bail out early during iteration when ctx.Done() is closed.

Copilot uses AI. Check for mistakes.
Comment thread pkg/cli/logs_orchestrator.go Outdated
Comment on lines +73 to +76
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to clean up old run folders: %v", cleanErr)))
} else if removed > 0 {
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Removed %d cached run folder(s) older than %s", removed, after)))
} else if verbose {
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

When --json is enabled, this cleanup block can write to stderr (success/info/warn) before the JSON payload is written to stdout (JSON is rendered much later, or in the no-runs path). This can break consumers that redirect stdout+stderr and expect the combined stream to start with valid JSON (see logs_json_stderr_order_test.go). Consider suppressing all console/stderr cleanup messages when jsonOutput is true, or buffering/defer-printing them until after JSON has been emitted.

Suggested change
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to clean up old run folders: %v", cleanErr)))
} else if removed > 0 {
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Removed %d cached run folder(s) older than %s", removed, after)))
} else if verbose {
if !jsonOutput {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to clean up old run folders: %v", cleanErr)))
}
} else if removed > 0 {
if !jsonOutput {
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Removed %d cached run folder(s) older than %s", removed, after)))
}
} else if verbose && !jsonOutput {

Copilot uses AI. Check for mistakes.
Comment thread pkg/cli/logs_cache.go
Comment on lines +109 to +112
if !strings.HasPrefix(entry.Name(), "run-") {
continue
}

Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

The PR description says cleanup should only target directories matching the run-{ID} pattern, but the current check only enforces the run- prefix. This can accidentally delete unrelated directories like run-backup or run-temp. Consider validating that the suffix is a valid integer (e.g., strconv.ParseInt(strings.TrimPrefix(name, "run-"), 10, 64)), and skip entries that don’t parse cleanly.

Copilot uses AI. Check for mistakes.
…after flag

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Commit pushed: 9eef01a

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions
Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (284 new lines across pkg/cli/) but does not have a linked Architecture Decision Record (ADR).

The Design Decision Gate has analyzed the PR diff and generated a draft ADR to help you get started:

📄 Draft ADR: docs/adr/29037-time-bounded-cache-eviction-via-after-flag-in-logs-command.md

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-29037: Time-Bounded Cache Eviction via --after Flag in the Logs Command

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

📋 Draft ADR preview — key sections

Decision captured from the diff:

We will add a --after flag to gh aw logs that deletes all cached run-{ID} directories whose creation date predates the specified cutoff before proceeding with the normal download. We will reuse the existing workflow.ResolveRelativeDate helper for consistent date parsing. Cleanup failures are non-fatal.

Alternatives the draft considers:

  • Dedicated gh aw logs clean subcommand — separates destructive eviction from download but adds a new top-level entry point for a narrow utility concern
  • Time-range filter on downloads--after could filter which runs are downloaded rather than which cached folders are deleted, but --start-date already serves that role

Trade-offs flagged:

  • ⚠️ --after is ambiguous: sounds like a download filter, not a cache eviction trigger
  • ⚠️ Eviction is a side-effect of the download command; users cannot evict without also triggering a download attempt
📋 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., 29037-kebab-title.md for PR #29037).

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

References: §25090240769

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · ● 177.5K ·

@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 80/100

Excellent test quality

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

Test Classification Details

View all test classifications (3 tests)
Test File Classification Notes
TestCleanupOldRunFolders pkg/cli/logs_cache_test.go:47 ✅ Design Table-driven, 7 cases, extensive edge coverage
TestCleanupOldRunFoldersVerbose pkg/cli/logs_cache_test.go:136 ✅ Design Verifies verbose mode produces correct output
TestNewLogsCommand (modified) pkg/cli/logs_command_test.go:73 ✅ Design (borderline) Verifies --after flag is registered in CLI interface

Note: Signature-only changes in context_cancellation_test.go, logs_ci_scenario_test.go, logs_download_test.go, logs_json_stderr_order_test.go (adding empty "" parameter to keep up with the new DownloadWorkflowLogs signature) are maintenance updates and were not scored as new test logic.


Highlights

TestCleanupOldRunFolders is the standout test in this PR. It uses the preferred table-driven pattern (t.Run) with 7 well-named sub-cases that cover the full behavioral contract of the new cleanupOldRunFolders function:

  • Removes folders older than the cutoff ✅
  • Keeps folders newer than the cutoff ✅
  • Removes multiple old folders ✅
  • Ignores non-run--prefixed directories ✅
  • Falls back to directory mtime when no run_summary.json exists (edge case) ✅
  • Empty output directory (edge case) ✅
  • Non-existent output directory (edge case) ✅

Minor Suggestions

  1. TestCleanupOldRunFoldersVerbose — Happy-path only. Consider adding a case where verbose=true is called on an empty or non-existent directory (same edge cases from the table-driven test) to fully cover the verbose code path. Low priority since the main table-driven test covers the logic; this test primarily guards against panics in verbose mode.

  2. TestNewLogsCommand addition — The --after flag assertion (assert.NotNil) checks registration but not the flag's default value or type. Consider adding assert.Equal(t, "", afterFlag.DefValue, "'after' flag should default to empty string") to lock down the expected default.


Language Support

Tests analyzed:

  • 🟨 JavaScript (*.test.cjs, *.test.js): 0 tests

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). The new logs_cache_test.go demonstrates high-quality behavioral testing with comprehensive edge case coverage for the new --after cleanup feature.


📖 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: §25090240791

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

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 80/100. Test quality is excellent — 0% of new tests are implementation tests (threshold: 30%). The new logs_cache_test.go is particularly strong with 7 well-structured table-driven cases covering the full behavioral contract of the --after cleanup feature.

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot review all comments

…, validate run-{int} suffix

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/70e1d806-6d60-4d13-b35c-c5cdc7d0f1df

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 29, 2026

@copilot review all comments

All three review comments addressed in commit 8b11e3a:

  1. Context check ordering — moved the ctx.Done() check to run before the cleanup block so a cancelled context never triggers disk scanning.
  2. JSON stderr ordering — all cleanup console messages are now gated on !jsonOutput, keeping stderr clean when --json is active.
  3. run-{integer} suffix validation — added strconv.ParseInt check after the run- prefix guard; directories like run-backup or run-temp are skipped. Added a dedicated test case to cover this.

@pelikhan pelikhan merged commit d60aaf4 into main Apr 29, 2026
@pelikhan pelikhan deleted the copilot/add-date-time-delta-argument-to-logs branch April 29, 2026 04:54
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