Skip to content

Temporary ID references in patch content are not resolved before git am #25130

Closed

Description

@wtgodbe

Temporary ID references in patch content are not resolved before git am

Summary

When a create_pull_request safe output references a create_issue temporary ID inside the patch content (i.e., in the actual code being committed), the #aw_* placeholder is not resolved to the real issue number. The framework correctly resolves temporary IDs in the PR body text, but the patch file is applied verbatim — so the placeholder string ends up in the committed source code.

Reproduction

  1. Create a workflow where the agent:
    • Calls create_issue with temporary_id: "aw_myid"
    • Calls create_pull_request with a patch that contains #aw_myid in the diff content (e.g., inside a string literal in source code)
  2. Run the workflow.
  3. Observe that the PR body correctly resolves #aw_myid → #66195 (or whatever issue number was assigned), but the committed code still contains the literal string #aw_myid.

Real-world example

In dotnet/aspnetcore's test-quarantine workflow, the agent quarantines a flaky test by adding a [QuarantinedTest] attribute that references the tracking issue:

[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/#aw_navqry1")]

The safe_outputs job creates issue #66195 and correctly resolves the reference in the PR body ("Associated issue: #66195"). But the committed code in the PR still contains the unresolved placeholder:

// Actual (wrong):
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/#aw_navqry1")]

// Expected:
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/66195")]

Root cause

In actions/setup/js/create_pull_request.cjs, replaceTemporaryIdReferences() is called on the PR body (~line 693):

processedBody = replaceTemporaryIdReferences(processedBody, tempIdMap, itemRepo);
core.info(`Resolved ${tempIdMap.size} temporary ID references in PR body`);

But it is never called on the patch content. The patch is read from disk (line 524), validated, and then applied directly via git am --3way (~line 1010) with the raw, unresolved content.

The safe_outputs log confirms this — note the asymmetry:

Resolved 1 temporary ID references in PR body        ← body is resolved ✓
...
+    [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/#aw_navqry1")]   ← patch is NOT resolved ✗
...
[command]/usr/bin/git am --3way /tmp/gh-aw/aw-quarantine-....patch
Patch applied successfully

Additional consideration: URL-context replacement

Even if the patch content were processed, the current replacement logic would produce an incorrect result in URL context. The TEMPORARY_ID_PATTERN in temporary_id.cjs (line 30) replaces #aw_navqry1 → #66195:

Input:  https://github.com/dotnet/aspnetcore/issues/#aw_navqry1
Output: https://github.com/dotnet/aspnetcore/issues/#66195   ← '#' makes this a URL fragment anchor
Correct: https://github.com/dotnet/aspnetcore/issues/66195   ← should be a path segment

In PR body markdown, #66195 is a valid GitHub issue shorthand. But inside a URL, the # character creates a fragment identifier instead of a path component, making the link point to the wrong location.

Suggested fix

In create_pull_request.cjs, before applying the patch (around line 995), resolve temporary IDs in the patch content:

  1. Read patchContent (already done at line 524)
  2. Apply a URL-aware replacement that handles both contexts:
    • URL context: Replace /#aw_XXX (preceded by /) with /NUMBER — no # prefix
    • Text context: Replace #aw_XXX with #NUMBER (existing behavior)
  3. Write the modified content back to patchFilePath before calling git am

A simple approach for step 2 could be to first replace the URL-context pattern, then fall through to the standard text-context pattern:

// URL-context: /issues/#aw_XXX → /issues/NUMBER
patchContent = patchContent.replace(/\/(#aw_[A-Za-z0-9_]{3,12})\b/gi, (match, tempIdWithHash) => {
  const tempId = tempIdWithHash.substring(1); // strip leading #
  const resolved = tempIdMap.get(normalizeTemporaryId(tempId));
  if (resolved) {
    return `/${resolved.number}`;
  }
  return match;
});

// Then apply standard text-context replacement
patchContent = replaceTemporaryIdReferences(patchContent, tempIdMap, itemRepo);

// Write back before git am
fs.writeFileSync(patchFilePath, patchContent, "utf8");

Note: modifying the patch content changes the diff lines but not the patch metadata (commit hashes, etc.), so git am may need --3way to handle any resulting mismatch — which is already the default path.

Environment

Activity

  1. locked and limited conversation to collaborators on Apr 7, 2026
  2. unlocked this conversation on Apr 7, 2026
  3. added a commit that references this issue on Apr 11, 2026
    d06a804
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions