Skip to content

trial: render logical repo in github-context prompt - #50640

Open
pelikhan with Copilot wants to merge 2 commits into
mainfrom
copilot/fix-logical-repo-context
Open

trial: render logical repo in github-context prompt#50640
pelikhan with Copilot wants to merge 2 commits into
mainfrom
copilot/fix-logical-repo-context

Conversation

Copilot AI commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

In trial mode, --logical-repo owner/target redirects checkout and safe-output targeting to the logical repo, but the agent prompt's <github-context>.repository block still reported the host repo. GitHub MCP calls that omit explicit owner/repo therefore silently operated against the host repo — undetectable from a green run.

The github-context prompt renders - **repository**: ${{ github.repository }} unconditionally, which resolves to the host repo. TrialLogicalRepo was only consumed by safe-outputs env and checkout generation, never reaching prompt rendering.

Changes

  • unified_prompt_step.go: when TrialMode && TrialLogicalRepo != "", post-process the github-context section via a new applyTrialLogicalRepoToGitHubContext helper that:
    • rewrites the - **repository**: line to the literal logical slug instead of ${{ github.repository }}
    • appends a directive inside <github-context> steering GitHub tool calls that omit owner/repo to the logical repo
  • Non-trial workflows are untouched — the ${{ github.repository }} expression is preserved and still flows through the expression extractor.
  • Tests: added coverage for the trial (logical repo rendered + directive present) and non-trial (host repo expression retained) paths.

Rendered output in trial mode:

{{#if github.repository}}
- **repository**: owner/target
{{/if}}
...
This workflow is running in trial mode. The repository above (owner/target) is the
logical target repository. When calling GitHub tools without an explicit owner/repo,
use this repository.

Notes

The issue also suggested optionally overriding the GitHub MCP server's default owner/repo. This PR takes the prompt-level fix as the minimal change that eliminates the silent mismatch; the added directive covers the MCP concern without a broader config change. If reviewers prefer enforcing the default at the MCP server level as well, that can be layered on top.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix agent prompt to point to logical repo with --logical-repo trial: render logical repo in github-context prompt Aug 5, 2026
@pelikhan
pelikhan marked this pull request as ready for review August 5, 2026 17:38
Copilot AI balanced review requested due to automatic review settings August 5, 2026 17:38
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (91 additions detected).

Copilot AI requested a review from pelikhan August 5, 2026 17:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The directive still permits omitted repository arguments, causing MCP calls to retain the host-repository default.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

Updates trial-mode prompts to identify the logical target repository.

Changes:

  • Rewrites GitHub context for logical-repo trials.
  • Adds trial and non-trial unit coverage.
File summaries
File Description
pkg/workflow/unified_prompt_step.go Rewrites repository context and adds trial guidance.
pkg/workflow/unified_prompt_step_test.go Tests trial and non-trial prompt behavior.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 3
  • Review effort level: Balanced

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

promptText = strings.Replace(promptText, repoLine, replacement, 1)

directive := fmt.Sprintf(
"\nThis workflow is running in trial mode. The repository above (%s) is the logical target repository. When calling GitHub tools without an explicit owner/repo, use this repository.\n",
Comment on lines +767 to +777
var githubContext string
for _, section := range sections {
if !section.IsFile && strings.Contains(section.Content, "github-context") {
githubContext = section.Content
break
}
}
require.NotEmpty(t, githubContext, "Should have a github-context section")

assert.Contains(t, githubContext, "**repository**:",
"github-context should report the repository via the github.repository expression")
Comment on lines +750 to +751
assert.Contains(t, githubContext, "trial mode",
"github-context should include a trial mode directive")

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Test Quality Sentinel Report 🧪

PR: #50640 — "trial: render logical repo in github-context prompt"
Test Coverage: 2 new behavioral tests added
Test Inflation Ratio: 60 test lines / 31 prod lines = 1.94:1 ✅ (under 2:1 threshold)

Summary

Two well-structured behavioral tests were added to verify trial-mode repository context rendering. Both tests:

  • Cover distinct behavioral contracts (trial mode ON vs. OFF)
  • Include descriptive failure messages
  • Test both positive and negative assertions
  • Avoid mocking, instead testing actual integration behavior
  • Are designed as complementary control tests

Quality Analysis

Score: 100/100 ✅ Excellent

Breakdown:

  • Design tests: 2/2 (100%) → 40 points
  • Edge-case coverage: 2/2 (100%) → 30 points
  • No duplicates → 20 points
  • Test inflation (1.94 < 2:1) → 10 points

Implementation tests: 0% (threshold: ≤30%)

Quality Signals ✅

  • No mock libraries — tests integration behavior, not implementation details
  • Complementary coverage — two tests form control pair (ON/OFF scenarios)
  • Assertion quality — all assertions include descriptive failure messages
  • Edge cases — both positive and negative assertions
  • Build tags — file has required //go:build !integration tag
  • Test inflation — 1.94:1 ratio is acceptable (< 2:1 threshold)

Test Details

TestCollectPromptSections_TrialLogicalRepoGitHubContext

  • Verifies trial mode with logical target routes agent prompts to correct repository
  • 4 assertions with descriptive messages
  • Tests design invariant: "In trial mode, agent's notion of repository = logical target, not host"
  • Covers edge case via negative assertions (what should NOT appear in output)

TestCollectPromptSections_NoTrialLogicalRepoKeepsHostRepo

  • Verifies default behavior without trial mode preserves standard github.repository reference
  • 3 assertions with descriptive messages
  • Control test ensuring normal workflow unaffected by trial mode feature
  • Negative assertion prevents regression in non-trial mode workflows

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — approving with two minor suggestions.

📋 Key Themes & Highlights

Key Themes

  • Silent no-op risk: strings.Replace won't error if the template line drifts; a log guard would surface future regressions early.
  • Assertion tightness: the directive assertion checks for "trial mode" but not the actual logical repo slug — a minimal addition would close the gap.

Positive Highlights

  • ✅ Clean, scoped helper with a well-chosen single-replacement call
  • ✅ Defensive guard (TrialMode && TrialLogicalRepo != "") prevents any impact on non-trial workflows
  • ✅ Both paths (trial and non-trial) are covered by tests
  • ✅ Fallback for missing close tag prevents data loss
> 🧠 *Reviewed using Matt Pocock's skills by [Matt Pocock Skills Reviewer](https://github.com/github/gh-aw/actions/runs/31030993419)* · sonnet46 · 24.2 AIC · ⌖ 7.97 AIC · ⊞ 7.1K > Comment /matt to run again

func applyTrialLogicalRepoToGitHubContext(promptText, logicalRepo string) string {
const repoLine = "- **repository**: ${{ github.repository }}"
replacement := "- **repository**: " + logicalRepo
promptText = strings.Replace(promptText, repoLine, replacement, 1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/diagnosing-bugs] Silent no-op if the repository line format ever drifts — strings.Replace returns the original string unchanged with no log or error.

💡 Suggestion

If the template changes (extra space, different casing, etc.) the replacement silently finds nothing, so the agent still sees the host repo with no indication anything failed.

Consider adding a guard:

replaced := strings.Replace(promptText, repoLine, replacement, 1)
if replaced == promptText {
    unifiedPromptLog.Printf("warning: repository line not found in github-context prompt; logical repo not applied")
}
promptText = replaced

@copilot please address this.


sections := compiler.collectPromptSections(data)

var githubContext string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] The trial-mode test asserts the directive contains the string "trial mode" but not the logical repo slug inside the directive text itself — a future rephrasing could drop the slug from the directive while still passing this test.

💡 Suggestion

Add a tighter assertion on the directive content:

assert.Contains(t, githubContext, "owner/target",
    "trial mode directive should reference the logical repo slug")

This ensures both the repository line and the directive consistently reference the logical repo.

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: Request changes — the fix has the same silent-failure shape as the bug it's fixing

💡 Themes

The intent is right (rewrite the reported repository in trial mode), but applyTrialLogicalRepoToGitHubContext rewrites the repository line via an exact-literal strings.Replace with no signal or fallback if the match fails. If the underlying template (github_context_prompt.md) ever drifts even slightly, this silently reverts to reporting the host repo — the exact "undetectable in a green run" failure mode described in the PR body — while still appending a directive that falsely claims a specific repo was substituted. There's also no test coverage for that drift scenario, nor for the interaction between the trial-mode injection and the checkout-list injection that share the same </github-context> insertion point. See inline comments for concrete fixes.

> 🔎 *Code quality review by [PR Code Quality Reviewer](https://github.com/github/gh-aw/actions/runs/31030993449)* · auto · 50.2 AIC · ⌖ 7.08 AIC · ⊞ 7.9K > Comment /review to run again

func applyTrialLogicalRepoToGitHubContext(promptText, logicalRepo string) string {
const repoLine = "- **repository**: ${{ github.repository }}"
replacement := "- **repository**: " + logicalRepo
promptText = strings.Replace(promptText, repoLine, replacement, 1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The strings.Replace on repoLine silently no-ops if the literal template string doesn't match, which would silently reintroduce the exact host-repo leak this PR exists to fix.

💡 Fix: detect and surface a failed replacement

strings.Replace(promptText, repoLine, replacement, 1) gives no signal whether a match occurred. If pkg/workflow/prompts/github_context_prompt.md is later edited (reformatted, reindented, or the ${{ github.repository }} expression is changed), this function will silently return the unmodified text — still containing ${{ github.repository }} — while still appending the directive claiming "The repository above (%s) is the logical target repository." That's an internally inconsistent, wrong prompt with no compile-time or runtime signal, i.e. exactly the silent host/logical-repo mismatch class of bug this PR was written to eliminate.

Suggested change:

newText := strings.Replace(promptText, repoLine, replacement, 1)
if newText == promptText {
    unifiedPromptLog.Printf("WARNING: could not rewrite repository line for trial logical repo %q; github-context template may have changed", logicalRepo)
    return promptText // don't append a directive that references a substitution that never happened
}
promptText = newText

// No run of four or more newlines (i.e., 3+ consecutive blank lines) anywhere.
assert.NotContains(t, output, "\n\n\n\n", "blank run should be capped throughout the output")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No test exercises the failure mode where repoLine doesn't match promptText, so a template-drift regression that silently reintroduces the host-repo leak would pass CI undetected.

💡 Add a regression test for template drift

Both new tests only check the "happy path" where githubContextPromptText contains the exact repoLine constant. Add a test that calls applyTrialLogicalRepoToGitHubContext directly with a prompt text that does not contain the literal - **repository**: ${{ github.repository }} line (e.g. simulating template drift), and assert on the desired behavior (e.g. no directive appended, or a specific fallback), once the fallback logic from the sibling comment is implemented. Without this, a future edit to github_context_prompt.md can silently break trial-mode's repo redirection with zero test signal — the exact "undetectable in a green run" failure class described in this PR's own description.

// logical target. Rewrite the reported repository so the agent's notion of "the
// repository" matches the logical target, and add a directive so GitHub MCP calls
// that omit owner/repo don't silently operate against the host repo.
if data.TrialMode && data.TrialLogicalRepo != "" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There's no test covering the case where trial-mode logical-repo injection and buildCheckoutsPromptContent both fire in the same run, even though both insert content at the same </github-context> anchor sequentially in this function.

💡 Add an interaction test

applyTrialLogicalRepoToGitHubContext inserts its directive before </github-context> first, then (a few lines later) the checkout-list injection does strings.LastIndex(combinedPromptText, closeTag) again and inserts its own content before the same tag. This ordering happens to work today (checkouts land after the trial directive, both still land before the closing tag), but there's no test asserting the combined output is well-formed (e.g. checkout content isn't inserted between the repository line and the trial directive, tags aren't duplicated, and the trial directive doesn't get needlessly re-scanned/altered by expression extraction due to ${{ ... }}-looking content it doesn't produce). Add a test with TrialMode: true, TrialLogicalRepo set, and non-empty CheckoutConfigs to lock in the expected ordering.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: trial logical repo in github-context prompt

The fix is well-scoped and correct: it patches only the trial-mode code path, leaves non-trial workflows untouched, and the tests cover both branches.

One non-blocking suggestion (inline): the strings.Replace call at line 800 silently no-ops if the expected repo line is absent from the prompt text. The directive is still appended, creating a misleading prompt that contradicts itself. Adding a log warning when the replacement doesn't occur would make template drift detectable at run time.> 🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 22.3 AIC · ⌖ 12.1 AIC · ⊞ 5.4K

func applyTrialLogicalRepoToGitHubContext(promptText, logicalRepo string) string {
const repoLine = "- **repository**: ${{ github.repository }}"
replacement := "- **repository**: " + logicalRepo
promptText = strings.Replace(promptText, repoLine, replacement, 1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If repoLine is not found in promptText (e.g., the template in github_context_prompt.md is later updated), strings.Replace silently no-ops and returns the original text unchanged. The host repo remains visible in the prompt, but the directive still says "The repository above (owner/target) is the logical target" — a direct contradiction that would mislead the agent with no observable failure.

Consider logging a warning when the replacement doesn't occur:

if !strings.Contains(promptText, repoLine) {
    unifiedPromptLog.Printf("WARNING: trial logical repo substitution skipped: repo line not found in github-context prompt")
}
promptText = strings.Replace(promptText, repoLine, replacement, 1)

@copilot please address this.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please resolve the remaining review feedback on this PR:

  • address the requested changes about the trial logical repo substitution silently no-oping if the template text drifts
  • after fixing that, refresh the branch if needed and run the pr-finisher skill

Run: https://github.com/github/gh-aw/actions/runs/31034308996

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 27.8 AIC · ⌖ 5.07 AIC · ⊞ 8.3K ·
Comment /souschef to run again

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

PR Triage: #50640

Category: feature | Risk: medium | Priority Score: 55/100 (impact 25, urgency 15, quality 15)
Recommended action: batch_review
Grouped with compile/trial cluster (50639, 50638). CI: blocked mergeable_state — check required review/status.

Generated by 🔧 PR Triage Agent · auto · 45.2 AIC · ⌖ 2.9 AIC · ⊞ 8K ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

trial: --logical-repo leaves <github-context>.repository pointing at the host repo

4 participants