From c18c4803c5f898e9b579a48e494db001803a1a07 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:01:23 +0000 Subject: [PATCH 01/14] feat: add maintenance activity report operation Agent-Logs-Url: https://github.com/github/gh-aw/sessions/267bf5c1-a299-43c5-be20-17cdb0ab2a0c Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 54 +++++++- actions/setup/js/run_activity_report.cjs | 129 ++++++++++++++++++ actions/setup/js/run_activity_report.test.cjs | 115 ++++++++++++++++ pkg/workflow/maintenance_workflow_test.go | 40 ++++-- pkg/workflow/maintenance_workflow_yaml.go | 53 ++++++- pkg/workflow/side_repo_maintenance.go | 50 ++++++- .../side_repo_maintenance_integration_test.go | 4 + 7 files changed, 431 insertions(+), 14 deletions(-) create mode 100644 actions/setup/js/run_activity_report.cjs create mode 100644 actions/setup/js/run_activity_report.test.cjs diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 59d99c41a74..1d63f82e62b 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -50,6 +50,7 @@ on: - 'upgrade' - 'safe_outputs' - 'create_labels' + - 'activity_report' - 'close_agentic_workflows_issues' - 'clean_cache_memories' - 'validate' @@ -61,7 +62,7 @@ on: workflow_call: inputs: operation: - description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, close_agentic_workflows_issues, clean_cache_memories, validate)' + description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, validate)' required: false type: string default: '' @@ -156,7 +157,7 @@ jobs: await main(); run_operation: - if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate' && (!(github.event.repository.fork)) }} + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate' && (!(github.event.repository.fork)) }} runs-on: ubuntu-slim permissions: actions: write @@ -311,6 +312,55 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/create_labels.cjs'); await main(); + activity_report: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity_report' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + actions: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Scripts + uses: ./actions/setup + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Setup Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: go.mod + cache: true + + - name: Build gh-aw + run: make build + + - name: Generate agentic workflow activity report + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_CMD_PREFIX: ./gh-aw + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/run_activity_report.cjs'); + await main(); + close_agentic_workflows_issues: if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'close_agentic_workflows_issues' && (!(github.event.repository.fork)) }} runs-on: ubuntu-slim diff --git a/actions/setup/js/run_activity_report.cjs b/actions/setup/js/run_activity_report.cjs new file mode 100644 index 00000000000..85c4ea15639 --- /dev/null +++ b/actions/setup/js/run_activity_report.cjs @@ -0,0 +1,129 @@ +// @ts-check +/// + +const { getErrorMessage, isRateLimitError } = require("./error_helpers.cjs"); +const { resolveExecutionOwnerRepo } = require("./repo_helpers.cjs"); +const { sanitizeContent } = require("./sanitize_content.cjs"); + +const ISSUE_TITLE = "[AW activity report]"; + +/** @typedef {{ key: string, heading: string, startDate: string, optionalOnRateLimit: boolean }} ActivityRange */ + +/** @type {ActivityRange[]} */ +const REPORT_RANGES = [ + { key: "24h", heading: "Last 24 hours", startDate: "-1d", optionalOnRateLimit: false }, + { key: "7d", heading: "Last 7 days", startDate: "-1w", optionalOnRateLimit: false }, + { key: "30d", heading: "Last 30 days", startDate: "-1mo", optionalOnRateLimit: true }, +]; + +/** + * @param {string} text + * @returns {boolean} + */ +function hasRateLimitText(text) { + return /\bapi rate limit\b|\brate limit exceeded\b|\bsecondary rate limit\b|\b429\b/i.test(text); +} + +/** + * Run the logs command for a configured report range. + * + * @param {string} bin + * @param {string[]} prefixArgs + * @param {string} repoSlug + * @param {ActivityRange} range + * @returns {Promise<{ heading: string, body: string }>} + */ +async function runRangeReport(bin, prefixArgs, repoSlug, range) { + const args = [...prefixArgs, "logs", "--repo", repoSlug, "--start-date", range.startDate, "--format", "markdown"]; + core.info(`Running: ${bin} ${args.join(" ")}`); + + try { + const result = await exec.getExecOutput(bin, args, { ignoreReturnCode: true }); + const output = `${result.stdout || ""}\n${result.stderr || ""}`.trim(); + const rateLimited = hasRateLimitText(output); + + if (result.exitCode === 0 && result.stdout.trim()) { + return { + heading: range.heading, + body: sanitizeContent(result.stdout.trim()), + }; + } + + if (rateLimited && range.optionalOnRateLimit) { + core.warning(`Skipping ${range.heading} report due to GitHub API rate limiting`); + return { + heading: range.heading, + body: "_Skipped due to GitHub API rate limiting._", + }; + } + + if (rateLimited) { + return { + heading: range.heading, + body: "_Could not generate this section due to GitHub API rate limiting._", + }; + } + + return { + heading: range.heading, + body: `_Report command failed (exit code ${result.exitCode})._\n\n\`\`\`\n${sanitizeContent(output || "No command output was captured.")}\n\`\`\``, + }; + } catch (error) { + const errorMessage = getErrorMessage(error); + const rateLimited = isRateLimitError(error) || hasRateLimitText(errorMessage); + + if (rateLimited && range.optionalOnRateLimit) { + core.warning(`Skipping ${range.heading} report due to GitHub API rate limiting`); + return { + heading: range.heading, + body: "_Skipped due to GitHub API rate limiting._", + }; + } + + if (rateLimited) { + return { + heading: range.heading, + body: "_Could not generate this section due to GitHub API rate limiting._", + }; + } + + return { + heading: range.heading, + body: `_Report command failed: ${sanitizeContent(errorMessage)}_`, + }; + } +} + +/** + * Generate an agentic workflow activity report issue. + * @returns {Promise} + */ +async function main() { + const cmdPrefixStr = process.env.GH_AW_CMD_PREFIX || "gh aw"; + const [bin, ...prefixArgs] = cmdPrefixStr.split(" ").filter(Boolean); + const { owner, repo } = resolveExecutionOwnerRepo(); + const repoSlug = `${owner}/${repo}`; + + core.info(`Generating agentic workflow activity report for ${repoSlug}`); + + const sections = []; + for (const range of REPORT_RANGES) { + sections.push(await runRangeReport(bin, prefixArgs, repoSlug, range)); + } + + const body = ["## Agentic workflow activity report", "", `Repository: \`${repoSlug}\``, `Generated at: ${new Date().toISOString()}`, "", ...sections.flatMap(section => ["---", "", `## ${section.heading}`, "", section.body, ""])].join( + "\n" + ); + + const createdIssue = await github.rest.issues.create({ + owner, + repo, + title: ISSUE_TITLE, + body, + labels: ["agentic-workflows"], + }); + + core.info(`Created issue #${createdIssue.data.number}: ${createdIssue.data.html_url}`); +} + +module.exports = { main, hasRateLimitText, runRangeReport }; diff --git a/actions/setup/js/run_activity_report.test.cjs b/actions/setup/js/run_activity_report.test.cjs new file mode 100644 index 00000000000..2514e40161e --- /dev/null +++ b/actions/setup/js/run_activity_report.test.cjs @@ -0,0 +1,115 @@ +// @ts-check +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; + +describe("run_activity_report", () => { + let originalGlobals; + let originalEnv; + let mockCore; + let mockGithub; + let mockContext; + let mockExec; + + beforeEach(() => { + originalEnv = { ...process.env }; + process.env.GH_AW_CMD_PREFIX = "gh aw"; + + originalGlobals = { + core: global.core, + github: global.github, + context: global.context, + exec: global.exec, + }; + + mockCore = { + info: vi.fn(), + warning: vi.fn(), + }; + mockGithub = { + rest: { + issues: { + create: vi.fn().mockResolvedValue({ + data: { number: 42, html_url: "https://github.com/testowner/testrepo/issues/42" }, + }), + }, + }, + }; + mockContext = { + repo: { + owner: "testowner", + repo: "testrepo", + }, + }; + mockExec = { + getExecOutput: vi.fn(), + }; + + global.core = mockCore; + global.github = mockGithub; + global.context = mockContext; + global.exec = mockExec; + }); + + afterEach(() => { + process.env = originalEnv; + global.core = originalGlobals.core; + global.github = originalGlobals.github; + global.context = originalGlobals.context; + global.exec = originalGlobals.exec; + vi.clearAllMocks(); + }); + + it("creates an activity report issue with all three time ranges", async () => { + mockExec.getExecOutput + .mockResolvedValueOnce({ stdout: "## 24h report\nok", stderr: "", exitCode: 0 }) + .mockResolvedValueOnce({ stdout: "## 7d report\nok", stderr: "", exitCode: 0 }) + .mockResolvedValueOnce({ stdout: "## 30d report\nok", stderr: "", exitCode: 0 }); + + const { main } = await import("./run_activity_report.cjs"); + await main(); + + expect(mockExec.getExecOutput).toHaveBeenCalledTimes(3); + expect(mockExec.getExecOutput).toHaveBeenNthCalledWith(1, "gh", expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1d", "--format", "markdown"]), expect.objectContaining({ ignoreReturnCode: true })); + expect(mockExec.getExecOutput).toHaveBeenNthCalledWith(2, "gh", expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1w", "--format", "markdown"]), expect.objectContaining({ ignoreReturnCode: true })); + expect(mockExec.getExecOutput).toHaveBeenNthCalledWith( + 3, + "gh", + expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1mo", "--format", "markdown"]), + expect.objectContaining({ ignoreReturnCode: true }) + ); + + expect(mockGithub.rest.issues.create).toHaveBeenCalledWith( + expect.objectContaining({ + owner: "testowner", + repo: "testrepo", + title: "[AW activity report]", + labels: ["agentic-workflows"], + }) + ); + + const issueBody = mockGithub.rest.issues.create.mock.calls[0][0].body; + expect(issueBody).toContain("## Last 24 hours"); + expect(issueBody).toContain("## Last 7 days"); + expect(issueBody).toContain("## Last 30 days"); + }); + + it("skips the 30-day query when rate limited", async () => { + mockExec.getExecOutput + .mockResolvedValueOnce({ stdout: "24h", stderr: "", exitCode: 0 }) + .mockResolvedValueOnce({ stdout: "7d", stderr: "", exitCode: 0 }) + .mockResolvedValueOnce({ stdout: "", stderr: "API rate limit exceeded", exitCode: 1 }); + + const { main } = await import("./run_activity_report.cjs"); + await main(); + + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Skipping Last 30 days report")); + const issueBody = mockGithub.rest.issues.create.mock.calls[0][0].body; + expect(issueBody).toContain("Skipped due to GitHub API rate limiting."); + }); + + it("detects rate limit text helper", async () => { + const { hasRateLimitText } = await import("./run_activity_report.cjs"); + expect(hasRateLimitText("API rate limit exceeded")).toBe(true); + expect(hasRateLimitText("secondary rate limit")).toBe(true); + expect(hasRateLimitText("normal output")).toBe(false); + }); +}); diff --git a/pkg/workflow/maintenance_workflow_test.go b/pkg/workflow/maintenance_workflow_test.go index 985d9b5a813..fcdcb1a60d5 100644 --- a/pkg/workflow/maintenance_workflow_test.go +++ b/pkg/workflow/maintenance_workflow_test.go @@ -282,9 +282,10 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { yaml := string(content) operationSkipCondition := `github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == ''` - operationRunCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate'` + operationRunCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate'` applySafeOutputsCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'safe_outputs'` createLabelsCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'create_labels'` + activityReportCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity_report'` closeAgenticWorkflowIssuesCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'close_agentic_workflows_issues'` cleanCacheMemoriesCondition := `github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories'` @@ -367,6 +368,17 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { } } + // activity_report job should be triggered when operation == 'activity_report' + activityReportIdx := strings.Index(yaml, "\n activity_report:") + if activityReportIdx == -1 { + t.Errorf("Job activity_report not found in generated workflow") + } else { + activityReportSection := yaml[activityReportIdx : activityReportIdx+runOpSectionSearchRange] + if !strings.Contains(activityReportSection, activityReportCondition) { + t.Errorf("Job activity_report should have the activation condition %q in:\n%s", activityReportCondition, activityReportSection) + } + } + // close_agentic_workflows_issues job should be triggered when operation == 'close_agentic_workflows_issues' closeAgenticWorkflowIssuesIdx := strings.Index(yaml, "\n close_agentic_workflows_issues:") if closeAgenticWorkflowIssuesIdx == -1 { @@ -398,6 +410,11 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { t.Error("workflow_dispatch operation choices should include 'validate'") } + // Verify activity_report is an option in the operation choices + if !strings.Contains(yaml, "- 'activity_report'") { + t.Error("workflow_dispatch operation choices should include 'activity_report'") + } + // Verify close_agentic_workflows_issues is an option in the operation choices if !strings.Contains(yaml, "- 'close_agentic_workflows_issues'") { t.Error("workflow_dispatch operation choices should include 'close_agentic_workflows_issues'") @@ -430,9 +447,10 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { // Verify run_operation job exposes outputs runOpIdx2 := strings.Index(yaml, "\n run_operation:") if runOpIdx2 != -1 { - runOpSection2 := yaml[runOpIdx2 : runOpIdx2+600] + runOpEnd := min(runOpIdx2+1200, len(yaml)) + runOpSection2 := yaml[runOpIdx2:runOpEnd] if !strings.Contains(runOpSection2, "outputs:\n operation: ${{ steps.record.outputs.operation }}") { - t.Errorf("run_operation job should declare operation output, got:\n%s", runOpSection2[:300]) + t.Errorf("run_operation job should declare operation output, got:\n%s", runOpSection2[:min(300, len(runOpSection2))]) } } @@ -678,12 +696,12 @@ func TestGenerateMaintenanceWorkflow_RunOperationCLICodegen(t *testing.T) { t.Fatalf("Expected maintenance workflow to be generated: %v", err) } yaml := string(content) - // run_operation, create_labels, validate_workflows, and compile_workflows should use the same setup-go version - // (all use getActionPin, not hardcoded pins). Exactly 4 occurrences expected. + // run_operation, create_labels, activity_report, validate_workflows, and compile_workflows should use the same setup-go version + // (all use getActionPin, not hardcoded pins). Exactly 5 occurrences expected. setupGoPin := getActionPin("actions/setup-go") occurrences := strings.Count(yaml, setupGoPin) - if occurrences != 4 { - t.Errorf("Expected exactly 4 occurrences of pinned setup-go ref %q (run_operation + create_labels + validate_workflows + compile_workflows), got %d in:\n%s", + if occurrences != 5 { + t.Errorf("Expected exactly 5 occurrences of pinned setup-go ref %q (run_operation + create_labels + activity_report + validate_workflows + compile_workflows), got %d in:\n%s", setupGoPin, occurrences, yaml) } }) @@ -1143,6 +1161,9 @@ func TestGenerateSideRepoMaintenanceWorkflow(t *testing.T) { if !strings.Contains(contentStr, "create_labels") { t.Errorf("Side-repo maintenance should include create_labels job, got content length %d", len(contentStr)) } + if !strings.Contains(contentStr, "activity_report") { + t.Errorf("Side-repo maintenance should include activity_report job, got content length %d", len(contentStr)) + } }) t.Run("no side-repo file generated when no current checkout", func(t *testing.T) { @@ -1172,7 +1193,7 @@ func TestGenerateSideRepoMaintenanceWorkflow(t *testing.T) { } }) - t.Run("side-repo generated without expires uses safe_outputs and create_labels only", func(t *testing.T) { + t.Run("side-repo generated without expires uses safe_outputs, create_labels, and activity_report", func(t *testing.T) { tmpDir := t.TempDir() workflowDataList := []*WorkflowData{ { @@ -1213,6 +1234,9 @@ func TestGenerateSideRepoMaintenanceWorkflow(t *testing.T) { if strings.Contains(contentStr, "close-expired-entities") { t.Errorf("Side-repo maintenance should NOT include close-expired-entities when no expires, got content length %d", len(contentStr)) } + if !strings.Contains(contentStr, "activity_report") { + t.Errorf("Side-repo maintenance should include activity_report when no expires, got content length %d", len(contentStr)) + } }) t.Run("expression-based repository does not generate side-repo maintenance", func(t *testing.T) { diff --git a/pkg/workflow/maintenance_workflow_yaml.go b/pkg/workflow/maintenance_workflow_yaml.go index 0415c3de54f..d80023fb915 100644 --- a/pkg/workflow/maintenance_workflow_yaml.go +++ b/pkg/workflow/maintenance_workflow_yaml.go @@ -59,6 +59,7 @@ on: - 'upgrade' - 'safe_outputs' - 'create_labels' + - 'activity_report' - 'close_agentic_workflows_issues' - 'clean_cache_memories' - 'validate' @@ -70,7 +71,7 @@ on: workflow_call: inputs: operation: - description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, close_agentic_workflows_issues, clean_cache_memories, validate)' + description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, validate)' required: false type: string default: '' @@ -195,8 +196,8 @@ jobs: `) // Add unified run_operation job for all dispatch operations except those with dedicated jobs - // (safe_outputs, create_labels, close_agentic_workflows_issues, clean_cache_memories, validate) - runOperationCondition := buildRunOperationCondition("safe_outputs", "create_labels", "close_agentic_workflows_issues", "clean_cache_memories", "validate") + // (safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, validate) + runOperationCondition := buildRunOperationCondition("safe_outputs", "create_labels", "activity_report", "close_agentic_workflows_issues", "clean_cache_memories", "validate") yaml.WriteString(` run_operation: if: ${{ ` + RenderCondition(runOperationCondition) + ` }} @@ -349,6 +350,52 @@ jobs: await main(); `) + // Add activity_report job for workflow_dispatch with operation == 'activity_report' + yaml.WriteString(` + activity_report: + if: ${{ ` + RenderCondition(buildDispatchOperationCondition("activity_report")) + ` }} + runs-on: ` + runsOnValue + ` + permissions: + actions: read + issues: write + steps: + - name: Checkout repository + uses: ` + getActionPin("actions/checkout") + ` + with: + persist-credentials: false + + - name: Setup Scripts + uses: ` + setupActionRef + ` + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: ` + getCachedActionPinFromResolver("actions/github-script", resolver) + ` + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + +`) + + yaml.WriteString(generateInstallCLISteps(actionMode, version, actionTag, resolver)) + yaml.WriteString(` - name: Generate agentic workflow activity report + uses: ` + getCachedActionPinFromResolver("actions/github-script", resolver) + ` + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_CMD_PREFIX: ` + getCLICmdPrefix(actionMode) + ` + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/run_activity_report.cjs'); + await main(); +`) + // Add close_agentic_workflows_issues job for workflow_dispatch with operation == 'close_agentic_workflows_issues' yaml.WriteString(` close_agentic_workflows_issues: diff --git a/pkg/workflow/side_repo_maintenance.go b/pkg/workflow/side_repo_maintenance.go index 4e8aa64082a..36a81474ceb 100644 --- a/pkg/workflow/side_repo_maintenance.go +++ b/pkg/workflow/side_repo_maintenance.go @@ -216,6 +216,7 @@ on: - '' - 'safe_outputs' - 'create_labels' + - 'activity_report' - 'validate' run_url: description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.' @@ -225,7 +226,7 @@ on: workflow_call: inputs: operation: - description: 'Optional maintenance operation to run (safe_outputs, create_labels, validate)' + description: 'Optional maintenance operation to run (safe_outputs, create_labels, activity_report, validate)' required: false type: string default: '' @@ -424,6 +425,53 @@ jobs: await main(); `) + // Add activity_report job for workflow_dispatch/workflow_call with operation == 'activity_report' + yaml.WriteString(` + activity_report: + if: ${{ ` + RenderCondition(buildDispatchOperationCondition("activity_report")) + ` }} + runs-on: ` + runsOnValue + ` + permissions: + actions: read + issues: write + steps: + - name: Checkout repository + uses: ` + getActionPin("actions/checkout") + ` + with: + persist-credentials: false + + - name: Setup Scripts + uses: ` + setupActionRef + ` + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: ` + getCachedActionPinFromResolver("actions/github-script", resolver) + ` + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + +`) + + yaml.WriteString(generateInstallCLISteps(actionMode, version, actionTag, resolver)) + yaml.WriteString(` - name: Generate agentic workflow activity report in target repository + uses: ` + getCachedActionPinFromResolver("actions/github-script", resolver) + ` + env: + GH_TOKEN: ` + token + ` + GH_AW_CMD_PREFIX: ` + getCLICmdPrefix(actionMode) + ` + GH_AW_TARGET_REPO_SLUG: "` + repoSlug + `" + with: + github-token: ` + token + ` + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/run_activity_report.cjs'); + await main(); +`) + // Add validate_workflows job for workflow_dispatch/workflow_call with operation == 'validate' validateRunsOnValue := FormatRunsOn(nil, "ubuntu-latest") yaml.WriteString(` diff --git a/pkg/workflow/side_repo_maintenance_integration_test.go b/pkg/workflow/side_repo_maintenance_integration_test.go index dda9d21e4ef..64c4fc94952 100644 --- a/pkg/workflow/side_repo_maintenance_integration_test.go +++ b/pkg/workflow/side_repo_maintenance_integration_test.go @@ -87,6 +87,10 @@ This workflow operates on a separate repository. assert.Contains(t, contentStr, "create_labels:", "generated workflow should include create_labels job") + // Must have activity_report job. + assert.Contains(t, contentStr, "activity_report:", + "generated workflow should include activity_report job") + // GH_AW_TARGET_REPO_SLUG must be wired with the correct slug. assert.Contains(t, contentStr, `GH_AW_TARGET_REPO_SLUG: "my-org/target-repo"`, "GH_AW_TARGET_REPO_SLUG should be set to the target repo slug") From 833a7c59209f8502d39afcb1f8725db5eab998c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:06:51 +0000 Subject: [PATCH 02/14] refactor: simplify activity report markdown assembly Agent-Logs-Url: https://github.com/github/gh-aw/sessions/267bf5c1-a299-43c5-be20-17cdb0ab2a0c Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/run_activity_report.cjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actions/setup/js/run_activity_report.cjs b/actions/setup/js/run_activity_report.cjs index 85c4ea15639..2fd1eccde36 100644 --- a/actions/setup/js/run_activity_report.cjs +++ b/actions/setup/js/run_activity_report.cjs @@ -111,9 +111,9 @@ async function main() { sections.push(await runRangeReport(bin, prefixArgs, repoSlug, range)); } - const body = ["## Agentic workflow activity report", "", `Repository: \`${repoSlug}\``, `Generated at: ${new Date().toISOString()}`, "", ...sections.flatMap(section => ["---", "", `## ${section.heading}`, "", section.body, ""])].join( - "\n" - ); + const headerLines = ["## Agentic workflow activity report", "", `Repository: \`${repoSlug}\``, `Generated at: ${new Date().toISOString()}`, ""]; + const sectionLines = sections.flatMap(section => ["---", "", `## ${section.heading}`, "", section.body, ""]); + const body = [...headerLines, ...sectionLines].join("\n"); const createdIssue = await github.rest.issues.create({ owner, From f18edc42d3b2f7a0f7ad33afe75aeaf3f58ae3f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:24:50 +0000 Subject: [PATCH 03/14] fix: use dashed activity-report operation and new status issue title Agent-Logs-Url: https://github.com/github/gh-aw/sessions/37d80ed2-4179-4462-ac6e-bbe23dc36ad5 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 8 ++++---- actions/setup/js/run_activity_report.cjs | 2 +- actions/setup/js/run_activity_report.test.cjs | 2 +- pkg/workflow/maintenance_workflow_test.go | 12 ++++++------ pkg/workflow/maintenance_workflow_yaml.go | 12 ++++++------ pkg/workflow/side_repo_maintenance.go | 8 ++++---- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 1d63f82e62b..fc2007d174d 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -50,7 +50,7 @@ on: - 'upgrade' - 'safe_outputs' - 'create_labels' - - 'activity_report' + - 'activity-report' - 'close_agentic_workflows_issues' - 'clean_cache_memories' - 'validate' @@ -62,7 +62,7 @@ on: workflow_call: inputs: operation: - description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, validate)' + description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity-report, close_agentic_workflows_issues, clean_cache_memories, validate)' required: false type: string default: '' @@ -157,7 +157,7 @@ jobs: await main(); run_operation: - if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate' && (!(github.event.repository.fork)) }} + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity-report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate' && (!(github.event.repository.fork)) }} runs-on: ubuntu-slim permissions: actions: write @@ -313,7 +313,7 @@ jobs: await main(); activity_report: - if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity_report' && (!(github.event.repository.fork)) }} + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity-report' && (!(github.event.repository.fork)) }} runs-on: ubuntu-slim permissions: actions: read diff --git a/actions/setup/js/run_activity_report.cjs b/actions/setup/js/run_activity_report.cjs index 2fd1eccde36..09a5766fc49 100644 --- a/actions/setup/js/run_activity_report.cjs +++ b/actions/setup/js/run_activity_report.cjs @@ -5,7 +5,7 @@ const { getErrorMessage, isRateLimitError } = require("./error_helpers.cjs"); const { resolveExecutionOwnerRepo } = require("./repo_helpers.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); -const ISSUE_TITLE = "[AW activity report]"; +const ISSUE_TITLE = "[aw] agentic status report"; /** @typedef {{ key: string, heading: string, startDate: string, optionalOnRateLimit: boolean }} ActivityRange */ diff --git a/actions/setup/js/run_activity_report.test.cjs b/actions/setup/js/run_activity_report.test.cjs index 2514e40161e..857357f9380 100644 --- a/actions/setup/js/run_activity_report.test.cjs +++ b/actions/setup/js/run_activity_report.test.cjs @@ -81,7 +81,7 @@ describe("run_activity_report", () => { expect.objectContaining({ owner: "testowner", repo: "testrepo", - title: "[AW activity report]", + title: "[aw] agentic status report", labels: ["agentic-workflows"], }) ); diff --git a/pkg/workflow/maintenance_workflow_test.go b/pkg/workflow/maintenance_workflow_test.go index fcdcb1a60d5..b1b33b02a34 100644 --- a/pkg/workflow/maintenance_workflow_test.go +++ b/pkg/workflow/maintenance_workflow_test.go @@ -282,10 +282,10 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { yaml := string(content) operationSkipCondition := `github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == ''` - operationRunCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate'` + operationRunCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity-report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate'` applySafeOutputsCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'safe_outputs'` createLabelsCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'create_labels'` - activityReportCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity_report'` + activityReportCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity-report'` closeAgenticWorkflowIssuesCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'close_agentic_workflows_issues'` cleanCacheMemoriesCondition := `github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories'` @@ -368,7 +368,7 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { } } - // activity_report job should be triggered when operation == 'activity_report' + // activity_report job should be triggered when operation == 'activity-report' activityReportIdx := strings.Index(yaml, "\n activity_report:") if activityReportIdx == -1 { t.Errorf("Job activity_report not found in generated workflow") @@ -410,9 +410,9 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { t.Error("workflow_dispatch operation choices should include 'validate'") } - // Verify activity_report is an option in the operation choices - if !strings.Contains(yaml, "- 'activity_report'") { - t.Error("workflow_dispatch operation choices should include 'activity_report'") + // Verify activity-report is an option in the operation choices + if !strings.Contains(yaml, "- 'activity-report'") { + t.Error("workflow_dispatch operation choices should include 'activity-report'") } // Verify close_agentic_workflows_issues is an option in the operation choices diff --git a/pkg/workflow/maintenance_workflow_yaml.go b/pkg/workflow/maintenance_workflow_yaml.go index d80023fb915..ae529a96275 100644 --- a/pkg/workflow/maintenance_workflow_yaml.go +++ b/pkg/workflow/maintenance_workflow_yaml.go @@ -59,7 +59,7 @@ on: - 'upgrade' - 'safe_outputs' - 'create_labels' - - 'activity_report' + - 'activity-report' - 'close_agentic_workflows_issues' - 'clean_cache_memories' - 'validate' @@ -71,7 +71,7 @@ on: workflow_call: inputs: operation: - description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, validate)' + description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity-report, close_agentic_workflows_issues, clean_cache_memories, validate)' required: false type: string default: '' @@ -196,8 +196,8 @@ jobs: `) // Add unified run_operation job for all dispatch operations except those with dedicated jobs - // (safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, validate) - runOperationCondition := buildRunOperationCondition("safe_outputs", "create_labels", "activity_report", "close_agentic_workflows_issues", "clean_cache_memories", "validate") + // (safe_outputs, create_labels, activity-report, close_agentic_workflows_issues, clean_cache_memories, validate) + runOperationCondition := buildRunOperationCondition("safe_outputs", "create_labels", "activity-report", "close_agentic_workflows_issues", "clean_cache_memories", "validate") yaml.WriteString(` run_operation: if: ${{ ` + RenderCondition(runOperationCondition) + ` }} @@ -350,10 +350,10 @@ jobs: await main(); `) - // Add activity_report job for workflow_dispatch with operation == 'activity_report' + // Add activity_report job for workflow_dispatch with operation == 'activity-report' yaml.WriteString(` activity_report: - if: ${{ ` + RenderCondition(buildDispatchOperationCondition("activity_report")) + ` }} + if: ${{ ` + RenderCondition(buildDispatchOperationCondition("activity-report")) + ` }} runs-on: ` + runsOnValue + ` permissions: actions: read diff --git a/pkg/workflow/side_repo_maintenance.go b/pkg/workflow/side_repo_maintenance.go index 36a81474ceb..06007252ebc 100644 --- a/pkg/workflow/side_repo_maintenance.go +++ b/pkg/workflow/side_repo_maintenance.go @@ -216,7 +216,7 @@ on: - '' - 'safe_outputs' - 'create_labels' - - 'activity_report' + - 'activity-report' - 'validate' run_url: description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.' @@ -226,7 +226,7 @@ on: workflow_call: inputs: operation: - description: 'Optional maintenance operation to run (safe_outputs, create_labels, activity_report, validate)' + description: 'Optional maintenance operation to run (safe_outputs, create_labels, activity-report, validate)' required: false type: string default: '' @@ -425,10 +425,10 @@ jobs: await main(); `) - // Add activity_report job for workflow_dispatch/workflow_call with operation == 'activity_report' + // Add activity_report job for workflow_dispatch/workflow_call with operation == 'activity-report' yaml.WriteString(` activity_report: - if: ${{ ` + RenderCondition(buildDispatchOperationCondition("activity_report")) + ` }} + if: ${{ ` + RenderCondition(buildDispatchOperationCondition("activity-report")) + ` }} runs-on: ` + runsOnValue + ` permissions: actions: read From bb16c881f6a1d9c4dc873424570fb0e1a2baa9e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:36:02 +0000 Subject: [PATCH 04/14] fix: use activity_report operation naming for consistency Agent-Logs-Url: https://github.com/github/gh-aw/sessions/f36a3c23-cf51-4452-8735-342ff7556fa0 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 8 ++++---- pkg/workflow/maintenance_workflow_test.go | 12 ++++++------ pkg/workflow/maintenance_workflow_yaml.go | 12 ++++++------ pkg/workflow/side_repo_maintenance.go | 8 ++++---- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index fc2007d174d..1d63f82e62b 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -50,7 +50,7 @@ on: - 'upgrade' - 'safe_outputs' - 'create_labels' - - 'activity-report' + - 'activity_report' - 'close_agentic_workflows_issues' - 'clean_cache_memories' - 'validate' @@ -62,7 +62,7 @@ on: workflow_call: inputs: operation: - description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity-report, close_agentic_workflows_issues, clean_cache_memories, validate)' + description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, validate)' required: false type: string default: '' @@ -157,7 +157,7 @@ jobs: await main(); run_operation: - if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity-report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate' && (!(github.event.repository.fork)) }} + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate' && (!(github.event.repository.fork)) }} runs-on: ubuntu-slim permissions: actions: write @@ -313,7 +313,7 @@ jobs: await main(); activity_report: - if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity-report' && (!(github.event.repository.fork)) }} + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity_report' && (!(github.event.repository.fork)) }} runs-on: ubuntu-slim permissions: actions: read diff --git a/pkg/workflow/maintenance_workflow_test.go b/pkg/workflow/maintenance_workflow_test.go index b1b33b02a34..fcdcb1a60d5 100644 --- a/pkg/workflow/maintenance_workflow_test.go +++ b/pkg/workflow/maintenance_workflow_test.go @@ -282,10 +282,10 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { yaml := string(content) operationSkipCondition := `github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == ''` - operationRunCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity-report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate'` + operationRunCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate'` applySafeOutputsCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'safe_outputs'` createLabelsCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'create_labels'` - activityReportCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity-report'` + activityReportCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity_report'` closeAgenticWorkflowIssuesCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'close_agentic_workflows_issues'` cleanCacheMemoriesCondition := `github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories'` @@ -368,7 +368,7 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { } } - // activity_report job should be triggered when operation == 'activity-report' + // activity_report job should be triggered when operation == 'activity_report' activityReportIdx := strings.Index(yaml, "\n activity_report:") if activityReportIdx == -1 { t.Errorf("Job activity_report not found in generated workflow") @@ -410,9 +410,9 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { t.Error("workflow_dispatch operation choices should include 'validate'") } - // Verify activity-report is an option in the operation choices - if !strings.Contains(yaml, "- 'activity-report'") { - t.Error("workflow_dispatch operation choices should include 'activity-report'") + // Verify activity_report is an option in the operation choices + if !strings.Contains(yaml, "- 'activity_report'") { + t.Error("workflow_dispatch operation choices should include 'activity_report'") } // Verify close_agentic_workflows_issues is an option in the operation choices diff --git a/pkg/workflow/maintenance_workflow_yaml.go b/pkg/workflow/maintenance_workflow_yaml.go index ae529a96275..d80023fb915 100644 --- a/pkg/workflow/maintenance_workflow_yaml.go +++ b/pkg/workflow/maintenance_workflow_yaml.go @@ -59,7 +59,7 @@ on: - 'upgrade' - 'safe_outputs' - 'create_labels' - - 'activity-report' + - 'activity_report' - 'close_agentic_workflows_issues' - 'clean_cache_memories' - 'validate' @@ -71,7 +71,7 @@ on: workflow_call: inputs: operation: - description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity-report, close_agentic_workflows_issues, clean_cache_memories, validate)' + description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, validate)' required: false type: string default: '' @@ -196,8 +196,8 @@ jobs: `) // Add unified run_operation job for all dispatch operations except those with dedicated jobs - // (safe_outputs, create_labels, activity-report, close_agentic_workflows_issues, clean_cache_memories, validate) - runOperationCondition := buildRunOperationCondition("safe_outputs", "create_labels", "activity-report", "close_agentic_workflows_issues", "clean_cache_memories", "validate") + // (safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, validate) + runOperationCondition := buildRunOperationCondition("safe_outputs", "create_labels", "activity_report", "close_agentic_workflows_issues", "clean_cache_memories", "validate") yaml.WriteString(` run_operation: if: ${{ ` + RenderCondition(runOperationCondition) + ` }} @@ -350,10 +350,10 @@ jobs: await main(); `) - // Add activity_report job for workflow_dispatch with operation == 'activity-report' + // Add activity_report job for workflow_dispatch with operation == 'activity_report' yaml.WriteString(` activity_report: - if: ${{ ` + RenderCondition(buildDispatchOperationCondition("activity-report")) + ` }} + if: ${{ ` + RenderCondition(buildDispatchOperationCondition("activity_report")) + ` }} runs-on: ` + runsOnValue + ` permissions: actions: read diff --git a/pkg/workflow/side_repo_maintenance.go b/pkg/workflow/side_repo_maintenance.go index 06007252ebc..36a81474ceb 100644 --- a/pkg/workflow/side_repo_maintenance.go +++ b/pkg/workflow/side_repo_maintenance.go @@ -216,7 +216,7 @@ on: - '' - 'safe_outputs' - 'create_labels' - - 'activity-report' + - 'activity_report' - 'validate' run_url: description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.' @@ -226,7 +226,7 @@ on: workflow_call: inputs: operation: - description: 'Optional maintenance operation to run (safe_outputs, create_labels, activity-report, validate)' + description: 'Optional maintenance operation to run (safe_outputs, create_labels, activity_report, validate)' required: false type: string default: '' @@ -425,10 +425,10 @@ jobs: await main(); `) - // Add activity_report job for workflow_dispatch/workflow_call with operation == 'activity-report' + // Add activity_report job for workflow_dispatch/workflow_call with operation == 'activity_report' yaml.WriteString(` activity_report: - if: ${{ ` + RenderCondition(buildDispatchOperationCondition("activity-report")) + ` }} + if: ${{ ` + RenderCondition(buildDispatchOperationCondition("activity_report")) + ` }} runs-on: ` + runsOnValue + ` permissions: actions: read From 4dacf4b6d3cb626c269a4718289f8a4b2f682c8b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:58:32 +0000 Subject: [PATCH 05/14] fix: improve activity report markdown structure and query depth Agent-Logs-Url: https://github.com/github/gh-aw/sessions/14261d82-e94d-44ea-a9cb-7ce48cf40810 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/run_activity_report.cjs | 20 ++++++++++++--- actions/setup/js/run_activity_report.test.cjs | 25 ++++++++++++++----- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/actions/setup/js/run_activity_report.cjs b/actions/setup/js/run_activity_report.cjs index 09a5766fc49..91132d836d7 100644 --- a/actions/setup/js/run_activity_report.cjs +++ b/actions/setup/js/run_activity_report.cjs @@ -6,6 +6,7 @@ const { resolveExecutionOwnerRepo } = require("./repo_helpers.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const ISSUE_TITLE = "[aw] agentic status report"; +const REPORT_COUNT = 100; /** @typedef {{ key: string, heading: string, startDate: string, optionalOnRateLimit: boolean }} ActivityRange */ @@ -34,7 +35,7 @@ function hasRateLimitText(text) { * @returns {Promise<{ heading: string, body: string }>} */ async function runRangeReport(bin, prefixArgs, repoSlug, range) { - const args = [...prefixArgs, "logs", "--repo", repoSlug, "--start-date", range.startDate, "--format", "markdown"]; + const args = [...prefixArgs, "logs", "--repo", repoSlug, "--start-date", range.startDate, "--count", String(REPORT_COUNT), "--format", "markdown"]; core.info(`Running: ${bin} ${args.join(" ")}`); try { @@ -45,7 +46,7 @@ async function runRangeReport(bin, prefixArgs, repoSlug, range) { if (result.exitCode === 0 && result.stdout.trim()) { return { heading: range.heading, - body: sanitizeContent(result.stdout.trim()), + body: normalizeReportMarkdown(sanitizeContent(result.stdout.trim())), }; } @@ -94,6 +95,17 @@ async function runRangeReport(bin, prefixArgs, repoSlug, range) { } } +/** + * Normalize report markdown for issue rendering. + * Demotes headings so top-level report headings start at H3. + * + * @param {string} markdown + * @returns {string} + */ +function normalizeReportMarkdown(markdown) { + return markdown.replace(/^(#{1,6})\s+/gm, (_, hashes) => `${"#".repeat(Math.min(6, hashes.length + 2))} `); +} + /** * Generate an agentic workflow activity report issue. * @returns {Promise} @@ -111,8 +123,8 @@ async function main() { sections.push(await runRangeReport(bin, prefixArgs, repoSlug, range)); } - const headerLines = ["## Agentic workflow activity report", "", `Repository: \`${repoSlug}\``, `Generated at: ${new Date().toISOString()}`, ""]; - const sectionLines = sections.flatMap(section => ["---", "", `## ${section.heading}`, "", section.body, ""]); + const headerLines = ["### Agentic workflow activity report", "", `Repository: \`${repoSlug}\``, `Generated at: ${new Date().toISOString()}`, ""]; + const sectionLines = sections.flatMap(section => ["
", `${section.heading}`, "", section.body, "", "
", ""]); const body = [...headerLines, ...sectionLines].join("\n"); const createdIssue = await github.rest.issues.create({ diff --git a/actions/setup/js/run_activity_report.test.cjs b/actions/setup/js/run_activity_report.test.cjs index 857357f9380..a68b29aab45 100644 --- a/actions/setup/js/run_activity_report.test.cjs +++ b/actions/setup/js/run_activity_report.test.cjs @@ -68,12 +68,22 @@ describe("run_activity_report", () => { await main(); expect(mockExec.getExecOutput).toHaveBeenCalledTimes(3); - expect(mockExec.getExecOutput).toHaveBeenNthCalledWith(1, "gh", expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1d", "--format", "markdown"]), expect.objectContaining({ ignoreReturnCode: true })); - expect(mockExec.getExecOutput).toHaveBeenNthCalledWith(2, "gh", expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1w", "--format", "markdown"]), expect.objectContaining({ ignoreReturnCode: true })); + expect(mockExec.getExecOutput).toHaveBeenNthCalledWith( + 1, + "gh", + expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1d", "--count", "100", "--format", "markdown"]), + expect.objectContaining({ ignoreReturnCode: true }) + ); + expect(mockExec.getExecOutput).toHaveBeenNthCalledWith( + 2, + "gh", + expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1w", "--count", "100", "--format", "markdown"]), + expect.objectContaining({ ignoreReturnCode: true }) + ); expect(mockExec.getExecOutput).toHaveBeenNthCalledWith( 3, "gh", - expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1mo", "--format", "markdown"]), + expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1mo", "--count", "100", "--format", "markdown"]), expect.objectContaining({ ignoreReturnCode: true }) ); @@ -87,9 +97,12 @@ describe("run_activity_report", () => { ); const issueBody = mockGithub.rest.issues.create.mock.calls[0][0].body; - expect(issueBody).toContain("## Last 24 hours"); - expect(issueBody).toContain("## Last 7 days"); - expect(issueBody).toContain("## Last 30 days"); + expect(issueBody).toContain("### Agentic workflow activity report"); + expect(issueBody).toContain("
"); + expect(issueBody).toContain("Last 24 hours"); + expect(issueBody).toContain("Last 7 days"); + expect(issueBody).toContain("Last 30 days"); + expect(issueBody).toContain("#### 24h report"); }); it("skips the 30-day query when rate limited", async () => { From a039b6790d5156742fe56989fd0b557c83df89d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:03:25 +0000 Subject: [PATCH 06/14] test: strengthen heading normalization coverage for activity report Agent-Logs-Url: https://github.com/github/gh-aw/sessions/14261d82-e94d-44ea-a9cb-7ce48cf40810 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/run_activity_report.cjs | 9 +++++++-- actions/setup/js/run_activity_report.test.cjs | 8 ++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/actions/setup/js/run_activity_report.cjs b/actions/setup/js/run_activity_report.cjs index 91132d836d7..1843466adb0 100644 --- a/actions/setup/js/run_activity_report.cjs +++ b/actions/setup/js/run_activity_report.cjs @@ -7,6 +7,7 @@ const { sanitizeContent } = require("./sanitize_content.cjs"); const ISSUE_TITLE = "[aw] agentic status report"; const REPORT_COUNT = 100; +const HEADING_DEMOTION_LEVELS = 2; /** @typedef {{ key: string, heading: string, startDate: string, optionalOnRateLimit: boolean }} ActivityRange */ @@ -103,7 +104,11 @@ async function runRangeReport(bin, prefixArgs, repoSlug, range) { * @returns {string} */ function normalizeReportMarkdown(markdown) { - return markdown.replace(/^(#{1,6})\s+/gm, (_, hashes) => `${"#".repeat(Math.min(6, hashes.length + 2))} `); + return markdown.replace(/^(#{1,6})\s+/gm, (_, hashes) => { + const headingLevel = hashes.length; + const demotedHeadingLevel = Math.min(6, headingLevel + HEADING_DEMOTION_LEVELS); + return `${"#".repeat(demotedHeadingLevel)} `; + }); } /** @@ -138,4 +143,4 @@ async function main() { core.info(`Created issue #${createdIssue.data.number}: ${createdIssue.data.html_url}`); } -module.exports = { main, hasRateLimitText, runRangeReport }; +module.exports = { main, hasRateLimitText, runRangeReport, normalizeReportMarkdown }; diff --git a/actions/setup/js/run_activity_report.test.cjs b/actions/setup/js/run_activity_report.test.cjs index a68b29aab45..72ab99bab8a 100644 --- a/actions/setup/js/run_activity_report.test.cjs +++ b/actions/setup/js/run_activity_report.test.cjs @@ -125,4 +125,12 @@ describe("run_activity_report", () => { expect(hasRateLimitText("secondary rate limit")).toBe(true); expect(hasRateLimitText("normal output")).toBe(false); }); + + it("demotes report headings by two levels", async () => { + const { normalizeReportMarkdown } = await import("./run_activity_report.cjs"); + const transformed = normalizeReportMarkdown("# H1\n## H2\n### H3"); + expect(transformed).toContain("### H1"); + expect(transformed).toContain("#### H2"); + expect(transformed).toContain("##### H3"); + }); }); From ba187934422ca3edae3291e060b4624af56ff9e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:16:07 +0000 Subject: [PATCH 07/14] feat: increase activity report depth and cache downloaded logs Agent-Logs-Url: https://github.com/github/gh-aw/sessions/907ed0e7-a686-4cc8-b8b5-9985215319bf Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 11 +++++++++++ actions/setup/js/run_activity_report.cjs | 11 +++++++---- actions/setup/js/run_activity_report.test.cjs | 6 +++--- pkg/workflow/maintenance_workflow_test.go | 9 +++++++++ pkg/workflow/maintenance_workflow_yaml.go | 11 +++++++++++ pkg/workflow/side_repo_maintenance.go | 11 +++++++++++ .../side_repo_maintenance_integration_test.go | 6 ++++++ 7 files changed, 58 insertions(+), 7 deletions(-) diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 1d63f82e62b..21f18014915 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -317,6 +317,7 @@ jobs: runs-on: ubuntu-slim permissions: actions: read + contents: read issues: write steps: - name: Checkout repository @@ -348,11 +349,21 @@ jobs: - name: Build gh-aw run: make build + - name: Cache activity report logs + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v5.0.5 + with: + path: ./.cache/gh-aw/activity-report-logs + key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }} + restore-keys: | + ${{ runner.os }}-activity-report-logs-${{ github.repository }}- + ${{ runner.os }}-activity-report-logs- + - name: Generate agentic workflow activity report uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_AW_CMD_PREFIX: ./gh-aw + GH_AW_ACTIVITY_REPORT_OUTPUT_DIR: ./.cache/gh-aw/activity-report-logs with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/actions/setup/js/run_activity_report.cjs b/actions/setup/js/run_activity_report.cjs index 1843466adb0..d2944beebc3 100644 --- a/actions/setup/js/run_activity_report.cjs +++ b/actions/setup/js/run_activity_report.cjs @@ -6,8 +6,9 @@ const { resolveExecutionOwnerRepo } = require("./repo_helpers.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const ISSUE_TITLE = "[aw] agentic status report"; -const REPORT_COUNT = 100; +const REPORT_COUNT = 1000; const HEADING_DEMOTION_LEVELS = 2; +const DEFAULT_REPORT_OUTPUT_DIR = "./.cache/gh-aw/activity-report-logs"; /** @typedef {{ key: string, heading: string, startDate: string, optionalOnRateLimit: boolean }} ActivityRange */ @@ -33,10 +34,11 @@ function hasRateLimitText(text) { * @param {string[]} prefixArgs * @param {string} repoSlug * @param {ActivityRange} range + * @param {string} outputDir * @returns {Promise<{ heading: string, body: string }>} */ -async function runRangeReport(bin, prefixArgs, repoSlug, range) { - const args = [...prefixArgs, "logs", "--repo", repoSlug, "--start-date", range.startDate, "--count", String(REPORT_COUNT), "--format", "markdown"]; +async function runRangeReport(bin, prefixArgs, repoSlug, range, outputDir) { + const args = [...prefixArgs, "logs", "--repo", repoSlug, "--start-date", range.startDate, "--count", String(REPORT_COUNT), "--output", outputDir, "--format", "markdown"]; core.info(`Running: ${bin} ${args.join(" ")}`); try { @@ -117,6 +119,7 @@ function normalizeReportMarkdown(markdown) { */ async function main() { const cmdPrefixStr = process.env.GH_AW_CMD_PREFIX || "gh aw"; + const reportOutputDir = process.env.GH_AW_ACTIVITY_REPORT_OUTPUT_DIR || DEFAULT_REPORT_OUTPUT_DIR; const [bin, ...prefixArgs] = cmdPrefixStr.split(" ").filter(Boolean); const { owner, repo } = resolveExecutionOwnerRepo(); const repoSlug = `${owner}/${repo}`; @@ -125,7 +128,7 @@ async function main() { const sections = []; for (const range of REPORT_RANGES) { - sections.push(await runRangeReport(bin, prefixArgs, repoSlug, range)); + sections.push(await runRangeReport(bin, prefixArgs, repoSlug, range, reportOutputDir)); } const headerLines = ["### Agentic workflow activity report", "", `Repository: \`${repoSlug}\``, `Generated at: ${new Date().toISOString()}`, ""]; diff --git a/actions/setup/js/run_activity_report.test.cjs b/actions/setup/js/run_activity_report.test.cjs index 72ab99bab8a..549d9caff83 100644 --- a/actions/setup/js/run_activity_report.test.cjs +++ b/actions/setup/js/run_activity_report.test.cjs @@ -71,19 +71,19 @@ describe("run_activity_report", () => { expect(mockExec.getExecOutput).toHaveBeenNthCalledWith( 1, "gh", - expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1d", "--count", "100", "--format", "markdown"]), + expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1d", "--count", "1000", "--output", "./.cache/gh-aw/activity-report-logs", "--format", "markdown"]), expect.objectContaining({ ignoreReturnCode: true }) ); expect(mockExec.getExecOutput).toHaveBeenNthCalledWith( 2, "gh", - expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1w", "--count", "100", "--format", "markdown"]), + expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1w", "--count", "1000", "--output", "./.cache/gh-aw/activity-report-logs", "--format", "markdown"]), expect.objectContaining({ ignoreReturnCode: true }) ); expect(mockExec.getExecOutput).toHaveBeenNthCalledWith( 3, "gh", - expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1mo", "--count", "100", "--format", "markdown"]), + expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1mo", "--count", "1000", "--output", "./.cache/gh-aw/activity-report-logs", "--format", "markdown"]), expect.objectContaining({ ignoreReturnCode: true }) ); diff --git a/pkg/workflow/maintenance_workflow_test.go b/pkg/workflow/maintenance_workflow_test.go index fcdcb1a60d5..00356b5a48b 100644 --- a/pkg/workflow/maintenance_workflow_test.go +++ b/pkg/workflow/maintenance_workflow_test.go @@ -377,6 +377,15 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { if !strings.Contains(activityReportSection, activityReportCondition) { t.Errorf("Job activity_report should have the activation condition %q in:\n%s", activityReportCondition, activityReportSection) } + if !strings.Contains(activityReportSection, "contents: read") { + t.Errorf("Job activity_report should include contents: read permission in:\n%s", activityReportSection) + } + } + if !strings.Contains(yaml, "Cache activity report logs") { + t.Errorf("Job activity_report should include a cache step in:\n%s", yaml) + } + if !strings.Contains(yaml, "GH_AW_ACTIVITY_REPORT_OUTPUT_DIR: ./.cache/gh-aw/activity-report-logs") { + t.Errorf("Job activity_report should set GH_AW_ACTIVITY_REPORT_OUTPUT_DIR in:\n%s", yaml) } // close_agentic_workflows_issues job should be triggered when operation == 'close_agentic_workflows_issues' diff --git a/pkg/workflow/maintenance_workflow_yaml.go b/pkg/workflow/maintenance_workflow_yaml.go index d80023fb915..60ef60c5c25 100644 --- a/pkg/workflow/maintenance_workflow_yaml.go +++ b/pkg/workflow/maintenance_workflow_yaml.go @@ -357,6 +357,7 @@ jobs: runs-on: ` + runsOnValue + ` permissions: actions: read + contents: read issues: write steps: - name: Checkout repository @@ -382,11 +383,21 @@ jobs: `) yaml.WriteString(generateInstallCLISteps(actionMode, version, actionTag, resolver)) + yaml.WriteString(` - name: Cache activity report logs + uses: ` + getActionPin("actions/cache") + ` + with: + path: ./.cache/gh-aw/activity-report-logs + key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }} + restore-keys: | + ${{ runner.os }}-activity-report-logs-${{ github.repository }}- + ${{ runner.os }}-activity-report-logs- +`) yaml.WriteString(` - name: Generate agentic workflow activity report uses: ` + getCachedActionPinFromResolver("actions/github-script", resolver) + ` env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_AW_CMD_PREFIX: ` + getCLICmdPrefix(actionMode) + ` + GH_AW_ACTIVITY_REPORT_OUTPUT_DIR: ./.cache/gh-aw/activity-report-logs with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/pkg/workflow/side_repo_maintenance.go b/pkg/workflow/side_repo_maintenance.go index 36a81474ceb..53122edd5b1 100644 --- a/pkg/workflow/side_repo_maintenance.go +++ b/pkg/workflow/side_repo_maintenance.go @@ -432,6 +432,7 @@ jobs: runs-on: ` + runsOnValue + ` permissions: actions: read + contents: read issues: write steps: - name: Checkout repository @@ -457,12 +458,22 @@ jobs: `) yaml.WriteString(generateInstallCLISteps(actionMode, version, actionTag, resolver)) + yaml.WriteString(` - name: Cache activity report logs + uses: ` + getActionPin("actions/cache") + ` + with: + path: ./.cache/gh-aw/activity-report-logs + key: ${{ runner.os }}-activity-report-logs-` + repoSlug + `-${{ github.ref_name }} + restore-keys: | + ${{ runner.os }}-activity-report-logs-` + repoSlug + `- + ${{ runner.os }}-activity-report-logs- +`) yaml.WriteString(` - name: Generate agentic workflow activity report in target repository uses: ` + getCachedActionPinFromResolver("actions/github-script", resolver) + ` env: GH_TOKEN: ` + token + ` GH_AW_CMD_PREFIX: ` + getCLICmdPrefix(actionMode) + ` GH_AW_TARGET_REPO_SLUG: "` + repoSlug + `" + GH_AW_ACTIVITY_REPORT_OUTPUT_DIR: ./.cache/gh-aw/activity-report-logs with: github-token: ` + token + ` script: | diff --git a/pkg/workflow/side_repo_maintenance_integration_test.go b/pkg/workflow/side_repo_maintenance_integration_test.go index 64c4fc94952..6d54679e621 100644 --- a/pkg/workflow/side_repo_maintenance_integration_test.go +++ b/pkg/workflow/side_repo_maintenance_integration_test.go @@ -90,6 +90,12 @@ This workflow operates on a separate repository. // Must have activity_report job. assert.Contains(t, contentStr, "activity_report:", "generated workflow should include activity_report job") + assert.Contains(t, contentStr, "Cache activity report logs", + "generated workflow should include cache step for activity_report logs") + assert.Contains(t, contentStr, "GH_AW_ACTIVITY_REPORT_OUTPUT_DIR: ./.cache/gh-aw/activity-report-logs", + "generated workflow should set GH_AW_ACTIVITY_REPORT_OUTPUT_DIR for activity_report logs") + assert.Contains(t, contentStr, "actions: read\n contents: read\n issues: write", + "activity_report job should include contents: read with explicit permissions") // GH_AW_TARGET_REPO_SLUG must be wired with the correct slug. assert.Contains(t, contentStr, `GH_AW_TARGET_REPO_SLUG: "my-org/target-repo"`, From 986870450bfa26d156d6b8446540a0755cf2eb81 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 22:04:42 +0000 Subject: [PATCH 08/14] chore: plan timeout update for activity report job Agent-Logs-Url: https://github.com/github/gh-aw/sessions/3ee21205-4c7f-42f8-bae5-186916afc5a1 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 21f18014915..e53af01a221 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -350,14 +350,13 @@ jobs: run: make build - name: Cache activity report logs - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v5.0.5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ./.cache/gh-aw/activity-report-logs key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }} restore-keys: | ${{ runner.os }}-activity-report-logs-${{ github.repository }}- ${{ runner.os }}-activity-report-logs- - - name: Generate agentic workflow activity report uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: From 0bf0d88f92a380adcc1c7c6b617430fb7ecb5d78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 22:12:06 +0000 Subject: [PATCH 09/14] feat: set 2-hour timeout for activity report jobs Agent-Logs-Url: https://github.com/github/gh-aw/sessions/3ee21205-4c7f-42f8-bae5-186916afc5a1 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 1 + pkg/workflow/maintenance_workflow_test.go | 3 +++ pkg/workflow/maintenance_workflow_yaml.go | 1 + pkg/workflow/side_repo_maintenance.go | 1 + pkg/workflow/side_repo_maintenance_integration_test.go | 2 ++ 5 files changed, 8 insertions(+) diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index e53af01a221..6f18c1a9a06 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -315,6 +315,7 @@ jobs: activity_report: if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity_report' && (!(github.event.repository.fork)) }} runs-on: ubuntu-slim + timeout-minutes: 120 permissions: actions: read contents: read diff --git a/pkg/workflow/maintenance_workflow_test.go b/pkg/workflow/maintenance_workflow_test.go index 00356b5a48b..a5750b65ac1 100644 --- a/pkg/workflow/maintenance_workflow_test.go +++ b/pkg/workflow/maintenance_workflow_test.go @@ -380,6 +380,9 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { if !strings.Contains(activityReportSection, "contents: read") { t.Errorf("Job activity_report should include contents: read permission in:\n%s", activityReportSection) } + if !strings.Contains(activityReportSection, "timeout-minutes: 120") { + t.Errorf("Job activity_report should set timeout-minutes: 120 in:\n%s", activityReportSection) + } } if !strings.Contains(yaml, "Cache activity report logs") { t.Errorf("Job activity_report should include a cache step in:\n%s", yaml) diff --git a/pkg/workflow/maintenance_workflow_yaml.go b/pkg/workflow/maintenance_workflow_yaml.go index 60ef60c5c25..b8aabcd0ac1 100644 --- a/pkg/workflow/maintenance_workflow_yaml.go +++ b/pkg/workflow/maintenance_workflow_yaml.go @@ -355,6 +355,7 @@ jobs: activity_report: if: ${{ ` + RenderCondition(buildDispatchOperationCondition("activity_report")) + ` }} runs-on: ` + runsOnValue + ` + timeout-minutes: 120 permissions: actions: read contents: read diff --git a/pkg/workflow/side_repo_maintenance.go b/pkg/workflow/side_repo_maintenance.go index 53122edd5b1..f3951e2330f 100644 --- a/pkg/workflow/side_repo_maintenance.go +++ b/pkg/workflow/side_repo_maintenance.go @@ -430,6 +430,7 @@ jobs: activity_report: if: ${{ ` + RenderCondition(buildDispatchOperationCondition("activity_report")) + ` }} runs-on: ` + runsOnValue + ` + timeout-minutes: 120 permissions: actions: read contents: read diff --git a/pkg/workflow/side_repo_maintenance_integration_test.go b/pkg/workflow/side_repo_maintenance_integration_test.go index 6d54679e621..4bcfc2399c4 100644 --- a/pkg/workflow/side_repo_maintenance_integration_test.go +++ b/pkg/workflow/side_repo_maintenance_integration_test.go @@ -96,6 +96,8 @@ This workflow operates on a separate repository. "generated workflow should set GH_AW_ACTIVITY_REPORT_OUTPUT_DIR for activity_report logs") assert.Contains(t, contentStr, "actions: read\n contents: read\n issues: write", "activity_report job should include contents: read with explicit permissions") + assert.Contains(t, contentStr, "timeout-minutes: 120", + "activity_report job should include a 2 hour timeout") // GH_AW_TARGET_REPO_SLUG must be wired with the correct slug. assert.Contains(t, contentStr, `GH_AW_TARGET_REPO_SLUG: "my-org/target-repo"`, From ea685c606544fbd2dda513951aa75d0666a1abcf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 22:15:46 +0000 Subject: [PATCH 10/14] chore: keep activity report cache pin stable in workflow yaml Agent-Logs-Url: https://github.com/github/gh-aw/sessions/3ee21205-4c7f-42f8-bae5-186916afc5a1 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 6f18c1a9a06..85434897f20 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -351,13 +351,14 @@ jobs: run: make build - name: Cache activity report logs - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v5.0.5 with: path: ./.cache/gh-aw/activity-report-logs key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }} restore-keys: | ${{ runner.os }}-activity-report-logs-${{ github.repository }}- ${{ runner.os }}-activity-report-logs- + - name: Generate agentic workflow activity report uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: From 116ffb92038017d93d48e917d1565fdb46e1b7a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 22:20:27 +0000 Subject: [PATCH 11/14] chore: plan cache key update for latest restore behavior Agent-Logs-Url: https://github.com/github/gh-aw/sessions/63d7139a-2204-468c-97f5-35631eb4bf35 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 85434897f20..6f18c1a9a06 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -351,14 +351,13 @@ jobs: run: make build - name: Cache activity report logs - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v5.0.5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ./.cache/gh-aw/activity-report-logs key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }} restore-keys: | ${{ runner.os }}-activity-report-logs-${{ github.repository }}- ${{ runner.os }}-activity-report-logs- - - name: Generate agentic workflow activity report uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: From f74b4061ac9c54240298a674afd5ac44d526f2a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 22:27:08 +0000 Subject: [PATCH 12/14] feat: use per-run cache keys for activity report logs Agent-Logs-Url: https://github.com/github/gh-aw/sessions/63d7139a-2204-468c-97f5-35631eb4bf35 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 2 +- pkg/workflow/maintenance_workflow_test.go | 4 ++++ pkg/workflow/maintenance_workflow_yaml.go | 2 +- pkg/workflow/side_repo_maintenance.go | 2 +- pkg/workflow/side_repo_maintenance_integration_test.go | 2 ++ 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 6f18c1a9a06..bce44757eaa 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -354,7 +354,7 @@ jobs: uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ./.cache/gh-aw/activity-report-logs - key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }} + key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: | ${{ runner.os }}-activity-report-logs-${{ github.repository }}- ${{ runner.os }}-activity-report-logs- diff --git a/pkg/workflow/maintenance_workflow_test.go b/pkg/workflow/maintenance_workflow_test.go index a5750b65ac1..30e70b42bb6 100644 --- a/pkg/workflow/maintenance_workflow_test.go +++ b/pkg/workflow/maintenance_workflow_test.go @@ -387,6 +387,10 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { if !strings.Contains(yaml, "Cache activity report logs") { t.Errorf("Job activity_report should include a cache step in:\n%s", yaml) } + if !strings.Contains(yaml, "${{ github.run_id }}-${{ github.run_attempt }}") { + t.Errorf("Job activity_report cache key should include run_id and run_attempt for latest-cache resolution in:\n%s", yaml) + } + if !strings.Contains(yaml, "GH_AW_ACTIVITY_REPORT_OUTPUT_DIR: ./.cache/gh-aw/activity-report-logs") { t.Errorf("Job activity_report should set GH_AW_ACTIVITY_REPORT_OUTPUT_DIR in:\n%s", yaml) } diff --git a/pkg/workflow/maintenance_workflow_yaml.go b/pkg/workflow/maintenance_workflow_yaml.go index b8aabcd0ac1..f3ee28eb7b0 100644 --- a/pkg/workflow/maintenance_workflow_yaml.go +++ b/pkg/workflow/maintenance_workflow_yaml.go @@ -388,7 +388,7 @@ jobs: uses: ` + getActionPin("actions/cache") + ` with: path: ./.cache/gh-aw/activity-report-logs - key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }} + key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: | ${{ runner.os }}-activity-report-logs-${{ github.repository }}- ${{ runner.os }}-activity-report-logs- diff --git a/pkg/workflow/side_repo_maintenance.go b/pkg/workflow/side_repo_maintenance.go index f3951e2330f..a09c2f4a15d 100644 --- a/pkg/workflow/side_repo_maintenance.go +++ b/pkg/workflow/side_repo_maintenance.go @@ -463,7 +463,7 @@ jobs: uses: ` + getActionPin("actions/cache") + ` with: path: ./.cache/gh-aw/activity-report-logs - key: ${{ runner.os }}-activity-report-logs-` + repoSlug + `-${{ github.ref_name }} + key: ${{ runner.os }}-activity-report-logs-` + repoSlug + `-${{ github.ref_name }}-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: | ${{ runner.os }}-activity-report-logs-` + repoSlug + `- ${{ runner.os }}-activity-report-logs- diff --git a/pkg/workflow/side_repo_maintenance_integration_test.go b/pkg/workflow/side_repo_maintenance_integration_test.go index 4bcfc2399c4..19ac8eb8c9f 100644 --- a/pkg/workflow/side_repo_maintenance_integration_test.go +++ b/pkg/workflow/side_repo_maintenance_integration_test.go @@ -98,6 +98,8 @@ This workflow operates on a separate repository. "activity_report job should include contents: read with explicit permissions") assert.Contains(t, contentStr, "timeout-minutes: 120", "activity_report job should include a 2 hour timeout") + assert.Contains(t, contentStr, "${{ github.run_id }}-${{ github.run_attempt }}", + "activity_report cache key should include run id and attempt for latest-cache resolution") // GH_AW_TARGET_REPO_SLUG must be wired with the correct slug. assert.Contains(t, contentStr, `GH_AW_TARGET_REPO_SLUG: "my-org/target-repo"`, From aa64a4626f4d13c87fcecea65e49ec46d4fb4014 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 22:40:46 +0000 Subject: [PATCH 13/14] chore: use github.run_id in activity report cache keys Agent-Logs-Url: https://github.com/github/gh-aw/sessions/12531c2f-2b64-4d48-a924-0808d53f98c2 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentics-maintenance.yml | 2 +- pkg/workflow/maintenance_workflow_test.go | 4 ++-- pkg/workflow/maintenance_workflow_yaml.go | 2 +- pkg/workflow/side_repo_maintenance.go | 2 +- pkg/workflow/side_repo_maintenance_integration_test.go | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index bce44757eaa..226acf666f4 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -354,7 +354,7 @@ jobs: uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ./.cache/gh-aw/activity-report-logs - key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }}-${{ github.run_attempt }} + key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }} restore-keys: | ${{ runner.os }}-activity-report-logs-${{ github.repository }}- ${{ runner.os }}-activity-report-logs- diff --git a/pkg/workflow/maintenance_workflow_test.go b/pkg/workflow/maintenance_workflow_test.go index 30e70b42bb6..a8174de8eb0 100644 --- a/pkg/workflow/maintenance_workflow_test.go +++ b/pkg/workflow/maintenance_workflow_test.go @@ -387,8 +387,8 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { if !strings.Contains(yaml, "Cache activity report logs") { t.Errorf("Job activity_report should include a cache step in:\n%s", yaml) } - if !strings.Contains(yaml, "${{ github.run_id }}-${{ github.run_attempt }}") { - t.Errorf("Job activity_report cache key should include run_id and run_attempt for latest-cache resolution in:\n%s", yaml) + if !strings.Contains(yaml, "${{ github.run_id }}") { + t.Errorf("Job activity_report cache key should include run_id for latest-cache resolution in:\n%s", yaml) } if !strings.Contains(yaml, "GH_AW_ACTIVITY_REPORT_OUTPUT_DIR: ./.cache/gh-aw/activity-report-logs") { diff --git a/pkg/workflow/maintenance_workflow_yaml.go b/pkg/workflow/maintenance_workflow_yaml.go index f3ee28eb7b0..91a701fb911 100644 --- a/pkg/workflow/maintenance_workflow_yaml.go +++ b/pkg/workflow/maintenance_workflow_yaml.go @@ -388,7 +388,7 @@ jobs: uses: ` + getActionPin("actions/cache") + ` with: path: ./.cache/gh-aw/activity-report-logs - key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }}-${{ github.run_attempt }} + key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }} restore-keys: | ${{ runner.os }}-activity-report-logs-${{ github.repository }}- ${{ runner.os }}-activity-report-logs- diff --git a/pkg/workflow/side_repo_maintenance.go b/pkg/workflow/side_repo_maintenance.go index a09c2f4a15d..a490ee7a171 100644 --- a/pkg/workflow/side_repo_maintenance.go +++ b/pkg/workflow/side_repo_maintenance.go @@ -463,7 +463,7 @@ jobs: uses: ` + getActionPin("actions/cache") + ` with: path: ./.cache/gh-aw/activity-report-logs - key: ${{ runner.os }}-activity-report-logs-` + repoSlug + `-${{ github.ref_name }}-${{ github.run_id }}-${{ github.run_attempt }} + key: ${{ runner.os }}-activity-report-logs-` + repoSlug + `-${{ github.ref_name }}-${{ github.run_id }} restore-keys: | ${{ runner.os }}-activity-report-logs-` + repoSlug + `- ${{ runner.os }}-activity-report-logs- diff --git a/pkg/workflow/side_repo_maintenance_integration_test.go b/pkg/workflow/side_repo_maintenance_integration_test.go index 19ac8eb8c9f..9cdfcb3cec6 100644 --- a/pkg/workflow/side_repo_maintenance_integration_test.go +++ b/pkg/workflow/side_repo_maintenance_integration_test.go @@ -98,8 +98,8 @@ This workflow operates on a separate repository. "activity_report job should include contents: read with explicit permissions") assert.Contains(t, contentStr, "timeout-minutes: 120", "activity_report job should include a 2 hour timeout") - assert.Contains(t, contentStr, "${{ github.run_id }}-${{ github.run_attempt }}", - "activity_report cache key should include run id and attempt for latest-cache resolution") + assert.Contains(t, contentStr, "${{ github.run_id }}", + "activity_report cache key should include run id for latest-cache resolution") // GH_AW_TARGET_REPO_SLUG must be wired with the correct slug. assert.Contains(t, contentStr, `GH_AW_TARGET_REPO_SLUG: "my-org/target-repo"`, From 7b8183fde6d6a3798fb3a5afc6c7a9ee90e41e87 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 23:26:53 +0000 Subject: [PATCH 14/14] chore: remove 30 day activity report section Agent-Logs-Url: https://github.com/github/gh-aw/sessions/398968c8-4b25-42c6-a1f1-6c382a43f2f3 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/run_activity_report.cjs | 1 - actions/setup/js/run_activity_report.test.cjs | 32 +++---------------- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/actions/setup/js/run_activity_report.cjs b/actions/setup/js/run_activity_report.cjs index d2944beebc3..27cd0cb920a 100644 --- a/actions/setup/js/run_activity_report.cjs +++ b/actions/setup/js/run_activity_report.cjs @@ -16,7 +16,6 @@ const DEFAULT_REPORT_OUTPUT_DIR = "./.cache/gh-aw/activity-report-logs"; const REPORT_RANGES = [ { key: "24h", heading: "Last 24 hours", startDate: "-1d", optionalOnRateLimit: false }, { key: "7d", heading: "Last 7 days", startDate: "-1w", optionalOnRateLimit: false }, - { key: "30d", heading: "Last 30 days", startDate: "-1mo", optionalOnRateLimit: true }, ]; /** diff --git a/actions/setup/js/run_activity_report.test.cjs b/actions/setup/js/run_activity_report.test.cjs index 549d9caff83..e858ee64d48 100644 --- a/actions/setup/js/run_activity_report.test.cjs +++ b/actions/setup/js/run_activity_report.test.cjs @@ -58,16 +58,13 @@ describe("run_activity_report", () => { vi.clearAllMocks(); }); - it("creates an activity report issue with all three time ranges", async () => { - mockExec.getExecOutput - .mockResolvedValueOnce({ stdout: "## 24h report\nok", stderr: "", exitCode: 0 }) - .mockResolvedValueOnce({ stdout: "## 7d report\nok", stderr: "", exitCode: 0 }) - .mockResolvedValueOnce({ stdout: "## 30d report\nok", stderr: "", exitCode: 0 }); + it("creates an activity report issue with 24h and 7d time ranges", async () => { + mockExec.getExecOutput.mockResolvedValueOnce({ stdout: "## 24h report\nok", stderr: "", exitCode: 0 }).mockResolvedValueOnce({ stdout: "## 7d report\nok", stderr: "", exitCode: 0 }); const { main } = await import("./run_activity_report.cjs"); await main(); - expect(mockExec.getExecOutput).toHaveBeenCalledTimes(3); + expect(mockExec.getExecOutput).toHaveBeenCalledTimes(2); expect(mockExec.getExecOutput).toHaveBeenNthCalledWith( 1, "gh", @@ -80,13 +77,6 @@ describe("run_activity_report", () => { expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1w", "--count", "1000", "--output", "./.cache/gh-aw/activity-report-logs", "--format", "markdown"]), expect.objectContaining({ ignoreReturnCode: true }) ); - expect(mockExec.getExecOutput).toHaveBeenNthCalledWith( - 3, - "gh", - expect.arrayContaining(["aw", "logs", "--repo", "testowner/testrepo", "--start-date", "-1mo", "--count", "1000", "--output", "./.cache/gh-aw/activity-report-logs", "--format", "markdown"]), - expect.objectContaining({ ignoreReturnCode: true }) - ); - expect(mockGithub.rest.issues.create).toHaveBeenCalledWith( expect.objectContaining({ owner: "testowner", @@ -101,24 +91,10 @@ describe("run_activity_report", () => { expect(issueBody).toContain("
"); expect(issueBody).toContain("Last 24 hours"); expect(issueBody).toContain("Last 7 days"); - expect(issueBody).toContain("Last 30 days"); + expect(issueBody).not.toContain("Last 30 days"); expect(issueBody).toContain("#### 24h report"); }); - it("skips the 30-day query when rate limited", async () => { - mockExec.getExecOutput - .mockResolvedValueOnce({ stdout: "24h", stderr: "", exitCode: 0 }) - .mockResolvedValueOnce({ stdout: "7d", stderr: "", exitCode: 0 }) - .mockResolvedValueOnce({ stdout: "", stderr: "API rate limit exceeded", exitCode: 1 }); - - const { main } = await import("./run_activity_report.cjs"); - await main(); - - expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Skipping Last 30 days report")); - const issueBody = mockGithub.rest.issues.create.mock.calls[0][0].body; - expect(issueBody).toContain("Skipped due to GitHub API rate limiting."); - }); - it("detects rate limit text helper", async () => { const { hasRateLimitText } = await import("./run_activity_report.cjs"); expect(hasRateLimitText("API rate limit exceeded")).toBe(true);