Skip to content

refactor: centralize prompt template path construction with getPromptPath helper#28589

Merged
pelikhan merged 3 commits intomainfrom
copilot/fix-duplicate-prompt-path
Apr 26, 2026
Merged

refactor: centralize prompt template path construction with getPromptPath helper#28589
pelikhan merged 3 commits intomainfrom
copilot/fix-duplicate-prompt-path

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 26, 2026

26 occurrences of ${process.env.RUNNER_TEMP}/gh-aw/prompts/<name> were scattered across 11 handler files with no support for GH_AW_PROMPTS_DIR overrides—meaning any change to runtime prompt location required edits in a dozen places.

Changes

  • New helper getPromptPath(name) in messages_core.cjs — resolves prompt template paths, preferring GH_AW_PROMPTS_DIR when set, falling back to ${RUNNER_TEMP}/gh-aw/prompts
  • Migrated all 26 call sites across handle_agent_failure.cjs (11), create_pull_request.cjs (3), handle_detection_runs.cjs (2), handle_noop_message.cjs (2), and 6 single-occurrence handlers
  • firewall_blocked_domains.cjs — updated to use getPromptPath while preserving its existing local dev fallback to __dirname/../md/ when neither env var is set
  • checkout_pr_branch.test.cjs — updated messages_core mock to include getPromptPath

Before / After

// Before — repeated in 26 places, no override support
const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/agent_timeout.md`;
return "\n" + renderTemplateFromFile(templatePath, { current_minutes, suggested_minutes });

// After
return "\n" + renderTemplateFromFile(getPromptPath("agent_timeout.md"), { current_minutes, suggested_minutes });

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:

  • invalid.example.invalid
    • Triggering command: /usr/lib/git-core/git-remote-https /usr/lib/git-core/git-remote-https origin https://invalid.example.invalid/nonexistent-repo.git git conf�� --local --get n-dir/git user.email test@example.com--git-dir=/tmp/bare-incremental-kL20BS rgo/bin/git git comm�� -m Initial commit k/gh-aw/gh-aw/actions/node_modules/.bin/git /tmp/bare-incremgit . ache/uv/0.11.7/xagent-change.txt git (dns block)
    • Triggering command: /usr/lib/git-core/git-remote-https /usr/lib/git-core/git-remote-https origin https://invalid.example.invalid/nonexistent-repo.git git conf�� user.email ode_modules/vitest/suppress-warnings.cjs rigin/token-option-test git-upload-pack git git-upload-pack push 64/bin/git ode_modules/viteorigin bran�� -M ion-test.patch.diff.tmp 86_64/git origin/auth-cleagit --stdout e_modules/.bin/gagent-change.txt git (dns block)
    • Triggering command: /usr/lib/git-core/git-remote-https /usr/lib/git-core/git-remote-https origin https://invalid.example.invalid/nonexistent-repo.git e/git init�� ndor/bin/git git ode_modules/.bin/git =receive test@example.com--git-dir=/tmp/bare-incremental-oiqJk8 /git (dns block)

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

Copilot AI linked an issue Apr 26, 2026 that may be closed by this pull request
5 tasks
…Path helper

Add getPromptPath(name) to messages_core.cjs that prefers GH_AW_PROMPTS_DIR
when set, otherwise falls back to ${RUNNER_TEMP}/gh-aw/prompts. Migrate all
26 repeated path construction sites across 11 handler files to use the helper.

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/8c62a493-c53a-4b24-9427-e8e7e986c45f

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix duplicate code in prompt template path construction refactor: centralize prompt template path construction with getPromptPath helper Apr 26, 2026
Copilot AI requested a review from gh-aw-bot April 26, 2026 12:18
@pelikhan pelikhan marked this pull request as ready for review April 26, 2026 15:15
Copilot AI review requested due to automatic review settings April 26, 2026 15:15
@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 72/100

⚠️ Acceptable — minor improvement opportunities

Metric Value
New/modified tests analyzed 4
✅ Design tests (behavioral contracts) 4 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 3 (75%)
Duplicate test clusters 0
Test inflation detected Yes (messages_core.test.cjs: 33 lines added vs 13 in production = 2.5:1)
🚨 Coding-guideline violations None

Test Classification Details

View all 4 tests
Test File Classification Issues Detected
should use RUNNER_TEMP when no override is set messages_core.test.cjs ✅ Design None — verifies base path construction
should prefer GH_AW_PROMPTS_DIR over RUNNER_TEMP messages_core.test.cjs ✅ Design Edge case: env var override priority
should use GH_AW_PROMPTS_DIR when RUNNER_TEMP is not set messages_core.test.cjs ✅ Design Edge case: fallback behavior
should include the template name in the path messages_core.test.cjs ✅ Design Overlaps with first test; minor duplication

Flagged Tests — Requires Review

No tests require mandatory changes. One minor improvement suggestion:

💡 Missing edge case: neither env var set

File: messages_core.test.cjs
Observation: All four tests set at least one of RUNNER_TEMP or GH_AW_PROMPTS_DIR. The behavior when neither is set is untested. If the function falls back to a default or throws, that contract is invisible.
Suggested improvement: Add a test case that clears both env vars and asserts the expected fallback behavior (error thrown, empty string, or hardcoded default path).

💡 Test inflation

File: messages_core.test.cjs vs messages_core.cjs
Observation: 33 new test lines vs 13 new production lines (ratio 2.5:1, threshold 2:1). This is borderline — the verbose import boilerplate per test (await import("./messages_core.cjs?" + Date.now())) accounts for much of the overhead.
Suggestion: Consider extracting the dynamic import into a shared beforeEach setup to reduce repetition and bring the ratio below threshold.


Language Support

Tests analyzed:

  • 🟨 JavaScript (*.test.cjs): 4 tests (vitest)
  • 🐹 Go (*_test.go): 0 tests changed

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). All new tests verify observable behavioral contracts of the getPromptPath helper (path construction under different environment variable combinations).


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

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

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: 72/100. Test quality is acceptable — 0% of new tests are implementation tests (threshold: 30%). All 4 new tests verify observable behavioral contracts of the getPromptPath helper.

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

This PR centralizes prompt template path construction by introducing a getPromptPath(name) helper (with GH_AW_PROMPTS_DIR override support) and migrating existing handlers away from hard-coded ${RUNNER_TEMP}/gh-aw/prompts/... paths.

Changes:

  • Added getPromptPath(name) to messages_core.cjs and unit tests for expected env-var precedence.
  • Updated multiple handlers to call getPromptPath(...) instead of inlining ${RUNNER_TEMP}/gh-aw/prompts/<name>.
  • Updated firewall_blocked_domains.cjs to use getPromptPath while keeping a source-tree fallback for local dev/tests, and updated mocks accordingly.
Show a summary per file
File Description
actions/setup/js/messages_core.cjs Adds getPromptPath helper and exports it for broad use.
actions/setup/js/messages_core.test.cjs Adds unit tests for getPromptPath env-var precedence and resets related env vars between tests.
actions/setup/js/setup_threat_detection.cjs Uses getPromptPath for the threat detection prompt template location.
actions/setup/js/push_to_pull_request_branch.cjs Uses getPromptPath for the manifest-protection fallback template.
actions/setup/js/handle_noop_message.cjs Uses getPromptPath for noop issue/comment templates.
actions/setup/js/handle_detection_runs.cjs Uses getPromptPath for detection-runs issue/comment templates.
actions/setup/js/handle_agent_failure.cjs Uses getPromptPath for multiple agent-failure context templates and issue/comment templates.
actions/setup/js/firewall_blocked_domains.cjs Switches to getPromptPath for runtime prompts while preserving source-tree fallback behavior.
actions/setup/js/create_report_incomplete_issue.cjs Uses getPromptPath for the report-incomplete issue template.
actions/setup/js/create_missing_tool_issue.cjs Uses getPromptPath for the missing-tool issue template.
actions/setup/js/create_missing_data_issue.cjs Uses getPromptPath for the missing-data issue template.
actions/setup/js/create_pull_request.cjs Uses getPromptPath for multiple manifest-protection and permission-denied fallback templates.
actions/setup/js/checkout_pr_branch.cjs Uses getPromptPath for the checkout failure step-summary template.
actions/setup/js/checkout_pr_branch.test.cjs Updates the messages_core.cjs mock to include getPromptPath.

Copilot's findings

Tip

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

  • Files reviewed: 14/14 changed files
  • Comments generated: 2

Comment on lines +94 to +97
function getPromptPath(name) {
const promptsDir = process.env.GH_AW_PROMPTS_DIR || `${process.env.RUNNER_TEMP}/gh-aw/prompts`;
return `${promptsDir}/${name}`;
}
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

getPromptPath() can produce an invalid path when neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set (e.g. "undefined/gh-aw/prompts/"). It also claims to return an absolute path but doesn’t enforce that. Consider validating that at least one base directory is configured (throwing a clear error), and build/normalize the path with path.join/path.resolve (also avoiding double slashes and platform separator issues).

Copilot uses AI. Check for mistakes.
let resolvedTemplatePath = templatePath;
if (!resolvedTemplatePath) {
resolvedTemplatePath = process.env.RUNNER_TEMP ? `${process.env.RUNNER_TEMP}/gh-aw/prompts/firewall_blocked_domains.md` : path.join(__dirname, "../md/firewall_blocked_domains.md");
resolvedTemplatePath = process.env.RUNNER_TEMP || process.env.GH_AW_PROMPTS_DIR ? getPromptPath("firewall_blocked_domains.md") : path.join(__dirname, "../md/firewall_blocked_domains.md");
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

The ternary condition process.env.RUNNER_TEMP || process.env.GH_AW_PROMPTS_DIR ? ... : ... relies on operator precedence and is a bit hard to scan. Consider adding parentheses (e.g. (process.env.RUNNER_TEMP || process.env.GH_AW_PROMPTS_DIR) ? ...) or extracting a boolean like hasRuntimePromptsDir to make the intent unambiguous.

Suggested change
resolvedTemplatePath = process.env.RUNNER_TEMP || process.env.GH_AW_PROMPTS_DIR ? getPromptPath("firewall_blocked_domains.md") : path.join(__dirname, "../md/firewall_blocked_domains.md");
resolvedTemplatePath = (process.env.RUNNER_TEMP || process.env.GH_AW_PROMPTS_DIR) ? getPromptPath("firewall_blocked_domains.md") : path.join(__dirname, "../md/firewall_blocked_domains.md");

Copilot uses AI. Check for mistakes.
@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot review all comments

- getPromptPath now throws a clear error when neither GH_AW_PROMPTS_DIR
  nor RUNNER_TEMP is set, preventing silent "undefined/gh-aw/prompts/..."
  paths
- firewall_blocked_domains.cjs: extract hasRuntimePromptsDir boolean to
  make the ternary intent unambiguous (Prettier-friendly alternative to
  parentheses)
- Update test fixtures for buildLockdownCheckFailedContext,
  buildStaleLockFileFailedContext, and buildTimeoutContext to set a
  dummy RUNNER_TEMP so the new guard doesn't fire
- Add test case asserting the new throw behaviour

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/934f6270-5038-4893-a6da-b8c8a34ce8b6

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

Copilot AI commented Apr 26, 2026

@copilot review all comments

Both review comments addressed in 43b09bd:

  1. getPromptPath silent invalid path — now throws "Cannot resolve prompt path: neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set" instead of producing undefined/gh-aw/prompts/.... A test case covers this.

  2. Ternary clarity in firewall_blocked_domains.cjs — extracted a hasRuntimePromptsDir boolean (Prettier strips redundant parens, so the variable approach is the stable way to express the intent unambiguously).

Three test fixtures in handle_agent_failure.test.cjs that mock fs.readFileSync but didn't set RUNNER_TEMP were also updated to provide a dummy value so the new guard doesn't fire.

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:

  • invalid.example.invalid
    • Triggering command: /usr/lib/git-core/git-remote-https origin https://invalid.example.invalid/nonexistent-repo.git (dns block)
    • Triggering command: /usr/lib/git-core/git-remote-https /usr/lib/git-core/git-remote-https origin https://invalid.example.invalid/nonexistent-repo.git e/git init�� it git ode_modules/.bin/git =receive Test User /git (dns block)
    • Triggering command: /usr/lib/git-core/git-remote-https /usr/lib/git-core/git-remote-https origin https://invalid.example.invalid/nonexistent-repo.git git init�� /opt/hostedtoolc-b git _modules/.bin/git user.email test@example.com--git-dir=/tmp/bare-incremental-1iljMk 64/bin/git git comm�� -m Initial commit tions/node_modules/.bin/git /tmp/bare-incremgit . cal/bin/git git (dns 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 26, 2026 15:35
@pelikhan pelikhan merged commit 7512713 into main Apr 26, 2026
12 checks passed
@pelikhan pelikhan deleted the copilot/fix-duplicate-prompt-path branch April 26, 2026 16:12
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.

Duplicate Code: Prompt template path construction

4 participants