Skip to content

feat: add cleanup-cache-memory job to agentics maintenance workflow#25908

Merged
pelikhan merged 7 commits intomainfrom
copilot/update-agentic-automation-cache-cleanup
Apr 12, 2026
Merged

feat: add cleanup-cache-memory job to agentics maintenance workflow#25908
pelikhan merged 7 commits intomainfrom
copilot/update-agentic-automation-cache-cleanup

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 12, 2026

Summary

Adds a new cleanup-cache-memory scheduled job to the agentics maintenance workflow that cleans out outdated caches created by cache-memory. The job can also be triggered on demand via the clean_cache_memories workflow_dispatch operation.

How it works

The new job runs on the same schedule as the existing maintenance tasks (every 2 hours) and can be manually triggered via the clean_cache_memories operation:

  1. Lists caches starting with memory- prefix via the GitHub Actions cache API, sorted newest-first by last_accessed_at
  2. Groups them by key prefix using parseCacheKey() which extracts both the group key and run ID in a single pass
  3. Keeps the latest entry per group (highest run ID = most recent run)
  4. Deletes outdated entries - all older caches in each group

Rate limit safety

  • Checks rate limit before starting via shared rate_limit_helpers.cjs and skips entirely if below threshold (100 remaining)
  • Logs rate limit snapshots via fetchAndLogRateLimit at start and end for observability
  • Includes 250ms delay between delete operations (reuses delay from expired_entity_cleanup_helpers.cjs)
  • Includes 100ms delay between list pagination requests
  • Re-checks rate limit every 10 deletions and stops early if it drops too low
  • Pagination capped at MAX_LIST_PAGES (50 pages / 5000 caches) to prevent runaway API usage

On-demand trigger

The clean_cache_memories operation is available in the workflow_dispatch inputs, allowing manual triggering of the cache cleanup job without running other scheduled maintenance tasks.

Changes

New files

  • actions/setup/js/cleanup_cache_memory.cjs - Cache cleanup script
  • actions/setup/js/cleanup_cache_memory.test.cjs - 21 unit tests covering:
    • Cache key parsing (run ID + group key extraction)
    • Cache grouping and deletion identification
    • Empty results handling
    • Rate limit skip behavior
    • Delete error handling and continuation
    • Pagination and upper bound enforcement
    • Rate limit early stop
    • Sort order verification
    • Logging verification
  • actions/setup/js/rate_limit_helpers.cjs - Shared rate limit checking utilities (getRateLimitRemaining, checkRateLimit, MIN_RATE_LIMIT_REMAINING) with fetchAndLogRateLimit integration
  • actions/setup/js/rate_limit_helpers.test.cjs - 6 unit tests for rate limit helpers

Modified files

  • pkg/workflow/maintenance_workflow.go - Adds cleanup-cache-memory job generation and clean_cache_memories operation
  • pkg/workflow/maintenance_workflow_test.go - Includes new job and operation in condition checks
  • .github/workflows/agentics-maintenance.yml - Regenerated with new job and operation

Copilot AI and others added 2 commits April 12, 2026 13:54
Add a new scheduled job that cleans outdated cache-memory caches:
- Lists all caches with "memory-" prefix via GitHub Actions API
- Groups caches by key prefix (everything before the run ID)
- Sorts by embedded run ID (last numeric segment)
- Keeps the latest entry per group, deletes the rest
- Includes timeouts between API calls to avoid throttling
- Skips if GitHub API rate limit is too low (< 100 remaining)
- Checks rate limit periodically during deletion and stops early if needed

Files added:
- actions/setup/js/cleanup_cache_memory.cjs - cleanup script
- actions/setup/js/cleanup_cache_memory.test.cjs - unit tests (20 tests)

Files modified:
- pkg/workflow/maintenance_workflow.go - add cleanup-cache-memory job
- pkg/workflow/maintenance_workflow_test.go - include new job in condition checks
- .github/workflows/agentics-maintenance.yml - regenerated

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/5d9226f6-d004-4ef1-b72a-d5ba94d545d5

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

@pelikhan pelikhan left a comment

Choose a reason for hiding this comment

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

@copilot add logging

* @param {number} ms - Milliseconds to wait
* @returns {Promise<void>}
*/
function delay(ms) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot find other delay impl, refactor

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Refactored in 56107ff — now imports delay from expired_entity_cleanup_helpers.cjs instead of duplicating it.

* @param {any} github - GitHub REST client
* @returns {Promise<number>} Remaining requests, or -1 on error
*/
async function getRateLimitRemaining(github) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot refactor to rate limiting helper file

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Extracted to rate_limit_helpers.cjs in 56107ff. Exports getRateLimitRemaining, checkRateLimit, and MIN_RATE_LIMIT_REMAINING. Also integrates fetchAndLogRateLimit for observability logging. Includes 6 unit tests.

* @param {string} key - Cache key string
* @returns {number | null} The extracted run ID, or null if not found
*/
function extractRunId(key) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot derive group and runid in 1 loop/function

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Combined into parseCacheKey(key) that returns { runId, groupKey } in a single pass in 56107ff.

let page = 1;
const perPage = 100;

while (true) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot have upper bound

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added MAX_LIST_PAGES = 50 constant (allows up to 5000 caches) and changed the loop to while (page <= MAX_LIST_PAGES) in 56107ff. Logs a warning if the limit is reached.

key: "memory-",
per_page: perPage,
page,
sort: "key",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot sort so that newest caches are returned first

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Changed API sort to last_accessed_at with direction: "desc" so newest caches are returned first, in 56107ff.

- Reuse delay from expired_entity_cleanup_helpers.cjs instead of duplicating
- Extract rate limit checking to shared rate_limit_helpers.cjs with tests
- Combine extractRunId/deriveGroupKey into single parseCacheKey function
- Add MAX_LIST_PAGES (50) upper bound on pagination loop
- Sort caches by last_accessed_at descending (newest first)
- Add fetchAndLogRateLimit logging at start and end of cleanup
- Add detailed logging: repository info, kept entries, page progress
- Log rate limit remaining during periodic checks

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/b4041e3a-bd6c-45f6-abaa-d48acfb07f3c

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

Copilot AI commented Apr 12, 2026

@copilot add logging

Added in 56107ff:

  • fetchAndLogRateLimit calls at start and end of cleanup for observability
  • Repository info logging (owner/repo)
  • Per-page fetch progress (Page N: X cache(s) fetched (Y total))
  • Kept entries logging (Keeping: key (run ID: N))
  • Rate limit remaining during periodic checks

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v0.47.4
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v0.47.4 --jq .object.sha (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from pelikhan April 12, 2026 14:19
@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot run on "clean_cache_memories" command

Add clean_cache_memories as a new workflow_dispatch operation so the
cleanup-cache-memory job can be triggered on demand. The job now runs
on schedule, empty operation, or the clean_cache_memories operation.
The run_operation job excludes this operation since it has its own
dedicated job.

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/5f72e332-33fd-4059-985e-30fe470376b7

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
// Add cleanup-cache-memory job for scheduled runs and clean_cache_memories operation
// This job lists all caches starting with "memory-", groups them by key prefix,
// keeps the latest run ID per group, and deletes the rest.
yaml.WriteString(`
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot use expression builder to generate optimize if expressions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Refactored in a83d229. The cleanup-cache-memory and run_operation if conditions are now built using composable expression builder functions (BuildAnd, BuildOr, BuildNotEquals, etc.) with RenderCondition optimizer. Added buildNotForkCondition, buildNotDispatchOrEmptyOperation, buildNotForkAndScheduledOrOperation, and buildRunOperationCondition helpers.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 12, 2026

@copilot run on "clean_cache_memories" command

Added clean_cache_memories as a workflow_dispatch operation in c53967e. The cleanup-cache-memory job now runs on schedule, empty operation, or the clean_cache_memories operation. The run_operation job excludes it since it has its own dedicated job.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw -embedcfg /tmp/go-build3353779091/b063/embedcfg -pack (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v7
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha d -n 10 (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha n-cache-cleanup --global 64/pkg/tool/linu-f (http block)
  • https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha (http block)
  • https://api.github.com/repos/docker/build-push-action/git/ref/tags/v7
    • Triggering command: /usr/bin/gh gh api /repos/docker/build-push-action/git/ref/tags/v7 --jq .object.sha --get-regexp --local 64/pkg/tool/linu-f (http block)
    • Triggering command: /usr/bin/gh gh api /repos/docker/build-push-action/git/ref/tags/v7 --jq .object.sha llector.slice --local r: $owner, name: $name) { hasDiscussionsEnabled } } (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v0.47.4
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v0.47.4 --jq .object.sha GOSUMDB GOWORK 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/githubnext/agentics/git/ref/tags/-
    • Triggering command: /usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/- --jq .object.sha (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI and others added 2 commits April 12, 2026 14:55
Replace hand-written if expressions for cleanup-cache-memory and
run_operation jobs with composable expression builder functions.
Introduces buildNotForkCondition, buildNotDispatchOrEmptyOperation,
buildNotForkAndScheduledOrOperation, and buildRunOperationCondition
helpers that leverage RenderCondition with optimizer.

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/564047d2-495a-4162-b2e2-6bc4f638dd34

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/564047d2-495a-4162-b2e2-6bc4f638dd34

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan pelikhan marked this pull request as ready for review April 12, 2026 15:00
Copilot AI review requested due to automatic review settings April 12, 2026 15:00
@pelikhan pelikhan merged commit 0c979e8 into main Apr 12, 2026
66 of 68 checks passed
@pelikhan pelikhan deleted the copilot/update-agentic-automation-cache-cleanup branch April 12, 2026 15:00
Copilot stopped work on behalf of pelikhan due to an error April 12, 2026 15: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 scheduled (and on-demand) maintenance job to clean up outdated GitHub Actions caches created by cache-memory, plus shared rate-limit helper utilities and accompanying tests.

Changes:

  • Adds cleanup-cache-memory job generation + a new clean_cache_memories workflow_dispatch operation.
  • Introduces cleanup_cache_memory.cjs cleanup script with unit tests covering parsing, grouping, pagination, rate-limit behavior, and error handling.
  • Adds shared rate_limit_helpers.cjs utilities with dedicated unit tests and regenerates the maintenance workflow YAML.
Show a summary per file
File Description
pkg/workflow/maintenance_workflow.go Generates the new cleanup job and dispatch operation; refactors run_operation condition generation.
pkg/workflow/maintenance_workflow_test.go Updates condition/operation choice assertions to include the new dedicated operation/job.
actions/setup/js/rate_limit_helpers.cjs New shared rate limit checking utilities used by the cleanup script.
actions/setup/js/rate_limit_helpers.test.cjs Unit tests for the new rate limit helpers.
actions/setup/js/cleanup_cache_memory.cjs New cache listing/grouping/deletion script with throttling + rate-limit guardrails.
actions/setup/js/cleanup_cache_memory.test.cjs Unit tests for cleanup behavior, pagination bounds, and logging.
.github/workflows/agentics-maintenance.yml Regenerated workflow with the new cleanup-cache-memory job and operation choice.

Copilot's findings

Tip

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

  • Files reviewed: 7/7 changed files
  • Comments generated: 4

Comment on lines +21 to +25
async function getRateLimitRemaining(github, operation = "rate_limit_check") {
try {
await fetchAndLogRateLimit(github, operation);
const { data } = await github.rest.rateLimit.get();
return data.rate.remaining;
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

getRateLimitRemaining() makes two rateLimit.get() API calls: one indirectly via fetchAndLogRateLimit() (which already calls github.rest.rateLimit.get()), then again immediately afterwards. This doubles rate-limit consumption per check and undermines the goal of being rate-limit conservative. Consider making a single rateLimit.get() call and using that response both for logging and for extracting data.rate.remaining (e.g., have fetchAndLogRateLimit return the response, or log from the same response without re-fetching).

Copilot uses AI. Check for mistakes.
Comment on lines +240 to +242
for (const cache of toDelete) {
// Check rate limit periodically (every 10 deletions)
if (deletedCount > 0 && deletedCount % 10 === 0) {
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

The periodic rate-limit check is keyed off deletedCount, so if deletions fail (and deletedCount doesn’t increment) the script may never re-check rate limits and could continue making API calls even as the limit drops. Track attempts/processed count (or use the loop index) for the modulo check instead of successful deletions.

Suggested change
for (const cache of toDelete) {
// Check rate limit periodically (every 10 deletions)
if (deletedCount > 0 && deletedCount % 10 === 0) {
for (let i = 0; i < toDelete.length; i++) {
const cache = toDelete[i];
// Check rate limit periodically (every 10 processed caches)
if (i > 0 && i % 10 === 0) {

Copilot uses AI. Check for mistakes.
* Lists all caches with "memory-" prefix, groups them by key prefix,
* keeps the latest run ID per group, and deletes the rest.
* Includes timeouts to avoid GitHub API throttling and skips
* if rate limiting is too high.
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

Docstring says "skips if rate limiting is too high" but the implementation skips when remaining rate limit is too low. Update the comment to match behavior to avoid confusion for future maintainers.

Suggested change
* if rate limiting is too high.
* if the remaining rate limit is too low.

Copilot uses AI. Check for mistakes.
*/
async function checkRateLimit(github, operation = "rate_limit_check") {
const remaining = await getRateLimitRemaining(github, operation);
if (remaining !== -1 && remaining < MIN_RATE_LIMIT_REMAINING) {
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

The checkRateLimit doc comment claims it "logs a warning" when the limit is too low, but the function currently only returns { ok: false } and does not emit any logs. Either adjust the comment or add the warning log here (so callers don’t need to duplicate messaging).

Suggested change
if (remaining !== -1 && remaining < MIN_RATE_LIMIT_REMAINING) {
if (remaining !== -1 && remaining < MIN_RATE_LIMIT_REMAINING) {
console.warn(
`[${operation}] Rate limit too low to continue: ${remaining} remaining (minimum ${MIN_RATE_LIMIT_REMAINING})`
);

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

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

Note: This gate ran after the PR merged. The draft ADR below should be committed to docs/adr/ as a follow-up to preserve the decision record.

AI has analyzed the PR diff and generated a draft ADR to help you get started:

📄 Draft ADR: docs/adr/25908-add-cache-memory-cleanup-maintenance-job.md

View draft ADR content
# ADR-25908: Add Cache-Memory Cleanup as a Scheduled Maintenance Job

**Date**: 2026-04-12
**Status**: Draft
**Deciders**: Unknown (inferred from PR #25908 by Copilot / pelikhan)

---

## Part 1 — Narrative (Human-Friendly)

### Context

The cache-memory feature stores agent working memory in GitHub Actions caches using keys of the form `memory-{context}-{runID}`. Each workflow run creates a new cache entry for its context group, but old entries from prior runs are never automatically removed. Over time this accumulates stale cache entries, consuming repository cache quota and potentially slowing cache-list operations. A dedicated cleanup mechanism is needed to prune outdated entries while preserving the most recent state for each context group.

### Decision

We will add a `cleanup-cache-memory` job to the existing agentics maintenance workflow that runs on the same two-hour schedule as other maintenance tasks. The job lists all caches with the `memory-` prefix, groups them by key prefix (everything except the trailing run ID), keeps only the entry with the highest run ID per group, and deletes all older entries. Rate limit safety is enforced through a shared `rate_limit_helpers.cjs` module: the job skips entirely if available rate limit is below 100 requests, introduces 250 ms delays between deletions, 100 ms delays between pagination requests, re-checks the rate limit every 10 deletions, and caps list pagination at 50 pages (5,000 caches). The `clean_cache_memories` workflow_dispatch operation allows the job to be triggered on demand independently of other scheduled tasks.

### Alternatives Considered

#### Alternative 1: Event-Driven Cleanup on Each Workflow Run

Cache cleanup could be triggered as a post-job step each time a workflow run that uses cache-memory completes — deleting all entries for the context group except the one just written. This eliminates stale caches immediately and avoids accumulation. It was not chosen because it couples cleanup logic into every workflow that writes to cache-memory, significantly increasing maintenance surface area and complicating the workflows themselves. It also introduces a risk of race conditions if two concurrent runs for the same context group try to clean up simultaneously.

#### Alternative 2: Time-Based Expiry via GitHub Cache TTL

GitHub Actions caches that have not been accessed in 7 days are automatically evicted by GitHub's platform. Relying on this built-in TTL would require no custom code. It was not chosen because the 7-day window can allow a large accumulation of stale entries in active repositories, and there is no way to reduce the TTL below 7 days or differentiate "latest" from "outdated" entries within the platform's native eviction policy.

#### Alternative 3: Delete All Entries Except the Most Recently Accessed

Instead of using the run ID embedded in the cache key as the version discriminator, the job could rely on the API's `last_accessed_at` sort order and keep only the first result per group prefix. It was not chosen because `last_accessed_at` reflects read access, not write recency — a cache entry read by a failed re-run could appear more recent than the entry representing the latest successful state. Using the run ID as an explicit version is more deterministic.

### Consequences

#### Positive
- Repository cache quota is recovered automatically on a two-hour schedule without manual intervention.
- Rate limit risk is bounded: the job skips when headroom is low, delays between API calls, and stops early on exhaustion.
- The `clean_cache_memories` dispatch operation allows operators to trigger cleanup immediately after a bulk operation.
- The shared `rate_limit_helpers.cjs` module consolidates rate-limit checking logic previously reimplemented in each maintenance script.

#### Negative
- The run ID embedded in the cache key becomes a load-bearing convention: any future change to the key format that removes or repositions the numeric run ID suffix will silently break `parseCacheKey()` grouping.
- Caches whose keys contain no numeric segment are silently skipped, which may hide unexpected key formats.
- The 50-page / 5,000-cache pagination cap means repositories with unusually large numbers of `memory-` caches may not be fully cleaned in a single run.

#### Neutral
- The job is generated by `pkg/workflow/maintenance_workflow.go` using the same templating approach as other jobs, consistent with the existing code-generation pattern.
- New helper functions (`buildNotForkCondition`, `buildNotForkAndScheduledOrOperation`, `buildRunOperationCondition`) were factored out to keep condition-building DRY across all maintenance jobs.
- 27 unit tests cover the new script and 6 tests cover the shared rate limit helpers.

What to do next

  1. Create the file docs/adr/25908-add-cache-memory-cleanup-maintenance-job.md on main with the draft content above
  2. Complete the missing sections — refine the decision rationale, confirm alternatives considered, and update deciders
  3. Change status from Draft to Accepted once the team has reviewed it

Why ADRs Matter

ADRs create a searchable, permanent record of why the codebase looks the way it does. The cache key convention (memory-{context}-{runID}) is now load-bearing for the cleanup logic — future contributors need to know this. So does anyone who changes the key format.


📋 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., 25908-add-cache-memory-cleanup-maintenance-job.md for PR #25908).

🔒 This gate ran post-merge. Please commit the ADR as a follow-up to preserve the architectural decision record.

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

@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 84/100

Excellent test quality

Metric Value
New/modified tests analyzed 28 (21 JS in cleanup_cache_memory.test.cjs, 6 JS in rate_limit_helpers.test.cjs, 1 modified Go test function)
✅ Design tests (behavioral contracts) 27 (96%)
⚠️ Implementation tests (low value) 1 (4%)
Tests with error/edge cases 14 (50%)
Duplicate test clusters 0
Test inflation detected No
🚨 Coding-guideline violations 0

Test Classification Details

📋 All 28 test cases (click to expand)
Test File Classification Notes
parseCacheKey – standard key cleanup_cache_memory.test.cjs ✅ Design Verifies parse output
parseCacheKey – integrity-aware key cleanup_cache_memory.test.cjs ✅ Design Verifies parse output
parseCacheKey – no numeric segment cleanup_cache_memory.test.cjs ✅ Design Edge case: null runId
parseCacheKey – only run ID cleanup_cache_memory.test.cjs ✅ Design Edge case: boundary input
identifyCachesToDelete – keep latest cleanup_cache_memory.test.cjs ✅ Design Core behavioral contract
identifyCachesToDelete – multiple groups cleanup_cache_memory.test.cjs ✅ Design Multi-group isolation
identifyCachesToDelete – single entry cleanup_cache_memory.test.cjs ✅ Design Edge case: no deletions
identifyCachesToDelete – null runId cleanup_cache_memory.test.cjs ✅ Design Edge case: skip null
identifyCachesToDelete – empty input cleanup_cache_memory.test.cjs ✅ Design Edge case: empty array
main – no caches found cleanup_cache_memory.test.cjs ✅ Design Verifies no deletes + summary
main – rate limit too low cleanup_cache_memory.test.cjs ✅ Design Error path: skip on low rate
main – deletes outdated caches cleanup_cache_memory.test.cjs ✅ Design Core happy-path E2E
main – all caches current cleanup_cache_memory.test.cjs ✅ Design Edge case: no-op scenario
main – handles delete errors gracefully cleanup_cache_memory.test.cjs ✅ Design Error path: partial failures
main – handles list error cleanup_cache_memory.test.cjs ✅ Design Error path: API failure
main – pagination cleanup_cache_memory.test.cjs ✅ Design Verifies multi-page fetch
main – rate limit stops early cleanup_cache_memory.test.cjs ✅ Design Edge case: mid-run rate exhaustion
main – log kept entries cleanup_cache_memory.test.cjs ✅ Design Verifies observable log output
main – log repository info cleanup_cache_memory.test.cjs ✅ Design Verifies observable log output
listMemoryCaches – sort order cleanup_cache_memory.test.cjs ✅ Design Behavioral contract: sort by last_accessed_at
listMemoryCaches – MAX_LIST_PAGES cleanup_cache_memory.test.cjs ✅ Design Guard against unbounded pagination
getRateLimitRemaining – success rate_limit_helpers.test.cjs ✅ Design Verifies return value
getRateLimitRemaining – error returns -1 rate_limit_helpers.test.cjs ✅ Design Error path: sentinel value
checkRateLimit – sufficient rate_limit_helpers.test.cjs ✅ Design Happy path
checkRateLimit – too low rate_limit_helpers.test.cjs ✅ Design Edge case: threshold boundary
checkRateLimit – API error → ok rate_limit_helpers.test.cjs ✅ Design Fail-open on error
MIN_RATE_LIMIT_REMAINING – is 100 rate_limit_helpers.test.cjs ⚠️ Implementation Tests a constant value
TestGenerateMaintenanceWorkflow_OperationJobConditions (modified) maintenance_workflow_test.go ✅ Design Verifies cleanup-cache-memory YAML conditions

Flagged Tests — Requires Review

⚠️ MIN_RATE_LIMIT_REMAINING should be 100 (rate_limit_helpers.test.cjs)

Classification: Implementation test
Issue: This test asserts expect(MIN_RATE_LIMIT_REMAINING).toBe(100) — it verifies a raw constant value, not a behavioral outcome. If the threshold is intentionally changed to 200 (for safety), this test will fail without any behavioral regression occurring.
What design invariant does this test enforce? None beyond "the constant is exactly 100 at this moment."
What would break if deleted? Nothing behaviorally — the real protection comes from checkRateLimit's tests that verify the skip-when-low behavior.
Suggested improvement: Replace or supplement with an assertion on observable behavior at the threshold boundary, e.g. verify that checkRateLimit returns ok: false when remaining equals exactly MIN_RATE_LIMIT_REMAINING - 1, and ok: true when equal. The boundary behavior matters more than the raw constant value.


Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 1 modified test function — unit (//go:build !integration ✅)
  • 🟨 JavaScript (*.test.cjs): 27 tests (vitest)

Scoring Breakdown

Component Score Calculation
Behavioral Coverage (40 pts) 39 27/28 design tests × 40
Error/Edge Case Coverage (30 pts) 15 14/28 tests with error/edge cases × 30
Low Duplication (20 pts) 20 0 duplicate clusters detected
Proportional Growth (10 pts) 10 All ratios < 2:1 (1.43×, 1.63×, 0.19×)
Total 84

Verdict

Check passed. 4% of new tests are implementation tests (threshold: 30%). The test suite demonstrates strong behavioral coverage — especially noteworthy is the comprehensive error-path and edge-case coverage in cleanup_cache_memory.test.cjs: paginated fetches, partial delete failures, rate-limit exhaustion mid-run, and null/empty input guards. The one weak spot is a constant-value test in rate_limit_helpers.test.cjs which can be improved but does not block merging.


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

🧪 Test quality analysis by Test Quality Sentinel · ● 1.2M ·

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: 84/100. Test quality is excellent — 4% of new tests are implementation tests (threshold: 30%). The new JavaScript tests for cleanup_cache_memory and rate_limit_helpers show strong behavioral coverage including error paths, edge cases, and pagination guards. One minor improvement opportunity: replace the constant-value test MIN_RATE_LIMIT_REMAINING should be 100 with a boundary-behavior assertion.

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