Skip to content

formalize cache-memory location naming convention and align prompt/docs with implementation#28482

Merged
pelikhan merged 4 commits intomainfrom
copilot/review-cache-memory-implementation
Apr 25, 2026
Merged

formalize cache-memory location naming convention and align prompt/docs with implementation#28482
pelikhan merged 4 commits intomainfrom
copilot/review-cache-memory-implementation

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 25, 2026

The cache directory path computation was duplicated 10+ times across three files with no single authoritative definition, making the convention implicit and prone to drift. Additionally, the agent-facing prompt templates and reference documentation had several inaccuracies relative to the actual implementation. A path-traversal vulnerability in the cache ID → directory path mapping was also identified and fixed.

Changes

Cache directory naming convention

  • New constants in cache.go:

    • defaultCacheMemoryDir = "/tmp/gh-aw/cache-memory" — canonical path for the default cache
    • cacheMemoryDirPrefix = "/tmp/gh-aw/cache-memory-" — prefix for named caches
  • New helper cacheMemoryDirFor(cacheID string) string — single source of truth for resolving a cache ID to its runtime path (no trailing slash; callers that need one for display context append it explicitly)

  • Replaced all 10 scattered if/else blocks in cache.go, claude_tools.go, and copilot_engine_execution.go

Cache ID validation (security fix)

  • isValidCacheID — new validation function enforcing ^[A-Za-z0-9_-]{1,64}$. Without this, a user-supplied ID such as ../../etc/passwd would be concatenated directly into a filesystem path, escaping /tmp/gh-aw.
  • Validation is enforced at parse time in parseCacheMemoryEntry, returning a clear error for invalid IDs.
  • Defence-in-depth: cacheMemoryDirFor panics if an invalid ID somehow bypasses parse-time validation.

Prompt template fixes

  • cache_memory_prompt.md and cache_memory_prompt_multi.md: added the missing __GH_AW_ALLOWED_EXTENSIONS__ placeholder. The Go code was computing and passing GH_AW_ALLOWED_EXTENSIONS but neither template had a slot for it, so agents were never informed about file-type restrictions. When allowed-extensions is configured the placeholder now renders as <allowed-extensions>.json, .txt</allowed-extensions>; it is empty when all extensions are allowed.

Documentation fixes (reference/cache-memory.md)

  • Removed ${{ github.run_id }} from the Advanced Configuration key example — the compiler explicitly rejects keys containing github.run_id with a validation error
  • Corrected the default key format description (uses GH_AW_WORKFLOW_ID_SANITIZED, not github.workflow)
  • Corrected the Multiple Configurations key default description
  • Replaced the incorrect "progressive dash-splitting" restore-keys description with the actual behavior: the compiler strips the run_id suffix to form a single restore-keys prefix; scope: repo adds a second key without the workflow ID

…helper function

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/1f6c6c24-eb6e-484f-b08f-424a7ff9d79d

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

@copilot review cache-memory instructions and prompt to match implementation.

@github-actions
Copy link
Copy Markdown
Contributor

Hey @Copilot 👋 — nice refactor! Consolidating the 10+ scattered if cache.ID == "default" blocks into a single cacheMemoryDirFor helper is a clear improvement for maintainability and reduces the risk of path drift.

One thing that would help get this across the finish line:

  • Add tests — the new cacheMemoryDirFor helper and the two new constants in cache.go aren't covered by test files in this diff. A small unit test covering the default, empty-string, and named-cache cases would give reviewers confidence the function behaves as documented and guards against regressions.

If you'd like a hand, you can assign this prompt to your coding agent:

Add unit tests for the new `cacheMemoryDirFor` function in `pkg/workflow/cache.go`.

Cover the following cases:
1. cacheID == "default" → should return "/tmp/gh-aw/cache-memory"
2. cacheID == "" (empty string) → should also return "/tmp/gh-aw/cache-memory" (safety net)
3. cacheID == "my-cache" (arbitrary named cache) → should return "/tmp/gh-aw/cache-memory-my-cache"

Place the tests in `pkg/workflow/cache_test.go` (or the existing test file for that package). Follow the existing test style in the file.

Generated by Contribution Check · ● 906.6K ·

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 25, 2026

@copilot review cache-memory instructions and prompt to match implementation.

Done in e2ba5c6. Issues found and fixed:

Prompt templates (cache_memory_prompt.md, cache_memory_prompt_multi.md):

  • Both were missing __GH_AW_ALLOWED_EXTENSIONS__ — the Go code was computing and passing the variable but the templates had no placeholder for it, so agents were never told about file-type restrictions. Now wraps as <allowed-extensions>.json, .txt</allowed-extensions> when configured, empty string when all extensions are allowed.

Documentation (reference/cache-memory.md):

  • Default key: removed the incorrect memory-${{ github.workflow }}-${{ github.run_id }} description (actual key uses GH_AW_WORKFLOW_ID_SANITIZED and includes an integrity prefix)
  • Advanced Configuration example: removed ${{ github.run_id }} from the user-supplied key — the compiler explicitly rejects keys containing github.run_id with a validation error
  • Multiple Configurations: corrected the key default description
  • Restore-keys behavior: replaced the incorrect "splits on dashes progressively" description with the actual behavior (strips just the run_id suffix; scope: repo adds a second key without the workflow ID)

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 GOMOD GOMODCACHE sh -c npx prettier --cGOINSECURE GOPROXY 64/bin/go GOSUMDB GOWORK 64/bin/go node (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 GOMOD GOMODCACHE node /opt�� prettier --check 64/bin/go **/*.ts **/*.json --ignore-path golangci-lint (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 GOMOD GOMODCACHE node /opt�� prettier --check 64/bin/go **/*.ts **/*.json --ignore-path go (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, .object.type] | @tsv GOSUMDB GOWORK 64/bin/go GOINSECURE GOMOD GOMODCACHE sh -c &#34;prettier&#34; --cheGOINSECURE GOPROXY 64/bin/go GOSUMDB GOWORK 64/bin/go /opt/hostedtoolcGO111MODULE (http block)
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq [.object.sha, .object.type] | @tsv --check **/*.cjs $name) { hasDiscussionsEnabled } } **/*.json --ignore-path ../../../.pretti--noprofile go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq [.object.sha, .object.type] | @tsv --check **/*.cjs 64/bin/go **/*.json --ignore-path ../../../.prettiGOPATH go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/github/gh-aw
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw --jq .default_branch -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE ache/go/1.25.8/x64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)

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

Copilot AI changed the title formalize cache-memory location naming convention formalize cache-memory location naming convention and align prompt/docs with implementation Apr 25, 2026
Copilot AI requested a review from pelikhan April 25, 2026 17:11
@pelikhan pelikhan marked this pull request as ready for review April 25, 2026 17:18
Copilot AI review requested due to automatic review settings April 25, 2026 17:18
@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 70/100

⚠️ Acceptable — good behavioral coverage, missing explicit error/edge-case paths in new tests

Metric Value
New/modified tests analyzed 2 new test functions
✅ Design tests (behavioral contracts) 2 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 0 (0%)
Duplicate test clusters 0
Test inflation detected No (test +40 / production +58, ratio 0.69)
🚨 Coding-guideline violations None

Test Classification Details

Test File Classification Issues Detected
TestBuildCacheMemoryPromptSection_SingleDefaultCacheWithAllowedExtensions pkg/workflow/cache_memory_prompt_test.go ✅ Design Happy-path only; no error/edge cases
TestBuildCacheMemoryPromptSection_MultipleCachesWithAllowedExtensions pkg/workflow/cache_memory_prompt_test.go ✅ Design Happy-path only; no error/edge cases

Also: two existing tests (_SingleDefaultCache, _MultipleCaches) each received an additional assert.Empty assertion for the new GH_AW_ALLOWED_EXTENSIONS env var — these are clean behavioral additions.


Flagged Tests — Requires Review

No tests flagged for guideline violations or implementation-test issues.

💡 Suggested Improvements (non-blocking)

Both new tests cover only the happy path. The buildCacheMemoryPromptSection function has edge cases worth testing:

  • Empty AllowedExtensions slice (already partially covered by the empty-string assertions added to existing tests — good)
  • Duplicate extensions across caches (e.g., both caches include .json) — the union/dedup behavior isn't tested
  • Single cache with one extension — boundary between singular and plural formatting

Example table-driven addition:

func TestBuildCacheMemoryPromptSection_AllowedExtensionsEdgeCases(t *testing.T) {
    tests := []struct {
        name     string
        caches   []CacheMemoryEntry
        expected string
    }{
        {name: "dedup across caches", caches: []CacheMemoryEntry{
            {ID: "a", AllowedExtensions: []string{".json"}},
            {ID: "b", AllowedExtensions: []string{".json", ".txt"}},
        }, expected: "\n<allowed-extensions>.json, .txt</allowed-extensions>"},
        {name: "empty slice is blank", caches: []CacheMemoryEntry\{\{ID: "a", AllowedExtensions: []string{}}}, expected: ""},
    }
    ...
}

Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 2 new test functions — unit (//go:build !integration) ✅ build tag present
  • 🟨 JavaScript: no test files changed

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). No coding-guideline violations detected. Score is 70/100 — acceptable, with an opportunity to add edge-case coverage for the allowed-extensions union logic.


📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

References: §24936314670

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

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: 70/100. Test quality is acceptable — 0% of new tests are implementation tests (threshold: 30%). Both new tests verify behavioral contracts (env var values for allowed-extensions feature). No coding-guideline violations. Minor suggestion: add edge-case tests for extension deduplication across caches.

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

Formalizes a single, canonical cache-memory directory naming convention and aligns prompt templates + reference docs with the actual cache-memory implementation.

Changes:

  • Centralized cache-memory path derivation via cacheMemoryDirFor() and replaced duplicated path logic across workflow compilation and tool-permission code.
  • Updated cache-memory prompt templates to include the __GH_AW_ALLOWED_EXTENSIONS__ placeholder and extended tests to cover the new substitution behavior.
  • Corrected cache-memory reference documentation around key defaults, run_id handling, and restore-keys behavior.
Show a summary per file
File Description
pkg/workflow/copilot_engine_execution.go Uses cacheMemoryDirFor() to add cache directories to Copilot CLI args consistently.
pkg/workflow/claude_tools.go Uses cacheMemoryDirFor() for per-cache filesystem tool allowlisting patterns.
pkg/workflow/cache_memory_prompt_test.go Adds tests ensuring GH_AW_ALLOWED_EXTENSIONS is rendered (or empty) as expected.
pkg/workflow/cache.go Introduces canonical cache-memory path constants + helper; updates prompt rendering to include allowed-extensions XML block.
docs/src/content/docs/reference/cache-memory.md Fixes inaccuracies in cache-memory key/restore-keys documentation and examples.
actions/setup/md/cache_memory_prompt_multi.md Adds __GH_AW_ALLOWED_EXTENSIONS__ placeholder to multi-cache prompt template.
actions/setup/md/cache_memory_prompt.md Adds __GH_AW_ALLOWED_EXTENSIONS__ placeholder to single-cache prompt template.
.github/workflows/ai-moderator.lock.yml Updates locked workflow prompt substitution env to match new allowed-extensions rendering format.

Copilot's findings

Tip

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

  • Files reviewed: 8/8 changed files
  • Comments generated: 1

Comment thread pkg/workflow/cache.go
Comment on lines +39 to 45
}
return cacheMemoryDirPrefix + cacheID
}

// validCacheMemoryScopes defines the allowed values for cache-memory scope
var validCacheMemoryScopes = []string{"workflow", "repo"}

…lization

Generated by Design Decision Gate [§24936314671].

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

Commit pushed: e0d7dce

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions
Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

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

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

📄 Draft ADR: docs/adr/28482-centralize-cache-memory-directory-path-computation.md

What to do next

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

    ADR: ADR-28482: Centralize Cache-Memory Directory Path Computation

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

Why ADRs Matter

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

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


📋 Michael Nygard ADR Format Reference

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

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

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 28482-centralize-cache-memory-directory-path-computation.md for PR #28482).

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

References: §24936314671

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

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot review all comments

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 25, 2026

@copilot review all comments

Addressed in 3de96e9. The code-review finding about path traversal was the main actionable item:

Added isValidCacheID validation (^[A-Za-z0-9_-]{1,64}$) enforced at two layers:

  1. Parse time (parseCacheMemoryEntry): rejects IDs like ../../etc, cache/sub, cache.mem with a clear error message
  2. Defence in depth (cacheMemoryDirFor): panics if an invalid ID somehow reaches path construction

Tests cover valid IDs, path-traversal patterns, slashes, dots, spaces, and the panic in cacheMemoryDirFor.

@pelikhan pelikhan merged commit 5f109b8 into main Apr 25, 2026
@pelikhan pelikhan deleted the copilot/review-cache-memory-implementation branch April 25, 2026 17:49
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