Skip to content

Resolve #aw_* temporary IDs during bundle-based signed commit replay#33181

Merged
pelikhan merged 12 commits into
mainfrom
copilot/fix-bundle-push-path-issue
May 19, 2026
Merged

Resolve #aw_* temporary IDs during bundle-based signed commit replay#33181
pelikhan merged 12 commits into
mainfrom
copilot/fix-bundle-push-path-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 19, 2026

Bundle transport was replaying commit file contents as-is, so #aw_* placeholders remained in committed code while patch transport correctly substituted them. This change brings parity by applying temporary ID substitution in the signed-commit replay path used by bundle-based pushes.

  • Signed replay now performs file-content substitution

    • Updated pushSignedCommits to rewrite temporary ID references in GraphQL createCommitOnBranch additions before mutation submission.
    • Handles URL-context and text-context replacements via existing temporary ID logic.
    • Only rewrites UTF-8 text payloads; binary/non-text blobs are left untouched.
  • Callers now provide substitution context

    • create_pull_request.cjs and push_to_pull_request_branch.cjs now pass:
      • resolvedTemporaryIds
      • currentRepo
    • This ensures bundle-based paths have the same resolution inputs as patch-based paths.
  • Regression coverage for the failing scenario

    • Added a focused test in push_signed_commits.test.cjs validating that replayed file content resolves both:
      • .../issues/#aw_xxx.../issues/<number>
      • #aw_xxx#<number>
await pushSignedCommits({
  githubClient,
  owner: "test-owner",
  repo: "test-repo",
  branch: "temp-id-branch",
  baseRef: "origin/main",
  cwd: workDir,
  resolvedTemporaryIds: { aw_test1: { repo: "test-owner/test-repo", number: 66708 } },
  currentRepo: "test-owner/test-repo",
});

Copilot AI and others added 4 commits May 19, 2026 00:52
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix temporary ID substitution in bundle-based push path Resolve #aw_* temporary IDs during bundle-based signed commit replay May 19, 2026
Copilot AI requested a review from pelikhan May 19, 2026 00:59
@pelikhan pelikhan marked this pull request as ready for review May 19, 2026 01:11
Copilot AI review requested due to automatic review settings May 19, 2026 01:11
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 pull request fixes bundle-based signed commit replay to resolve #aw_* temporary ID placeholders in file contents (matching the behavior already present in patch-based flows).

Changes:

  • Update pushSignedCommits to optionally rewrite temporary ID references inside UTF-8 text blob contents before submitting createCommitOnBranch.
  • Pass temporary ID substitution context (resolvedTemporaryIds, currentRepo) from bundle-related callers.
  • Add an integration test ensuring both URL-context and text-context temporary ID replacements occur during GraphQL replay.
Show a summary per file
File Description
actions/setup/js/push_signed_commits.cjs Adds temporary ID substitution for UTF-8 text additions during GraphQL signed commit replay.
actions/setup/js/create_pull_request.cjs Passes resolvedTemporaryIds and currentRepo into pushSignedCommits for bundle and non-bundle push paths.
actions/setup/js/push_to_pull_request_branch.cjs Passes resolvedTemporaryIds and currentRepo into pushSignedCommits when pushing to an existing PR branch.
actions/setup/js/push_signed_commits.test.cjs Adds regression coverage for resolving #aw_* placeholders in replayed file contents.

Copilot's findings

Tip

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

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

Comment on lines +145 to +152
map.set(normalizedKey, { repo: currentRepo, number: value });
continue;
}
if (value && typeof value === "object" && "number" in value) {
map.set(normalizedKey, {
repo: String("repo" in value && value.repo ? value.repo : currentRepo),
number: Number(value.number),
});

const { ERR_API } = require("./error_codes.cjs");
const { normalizeTemporaryId, replaceTemporaryIdReferencesInPatch } = require("./temporary_id.cjs");
const TEMPORARY_ID_REFERENCE_PATTERN = /#aw_[A-Za-z0-9_]{3,12}\b/i;
@github-actions github-actions Bot mentioned this pull request May 19, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 82/100

Excellent test quality

Metric Value
New/modified tests analyzed 1
✅ Design tests (behavioral contracts) 1 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 0 (0%)
Duplicate test clusters 0
Test inflation detected No
🚨 Coding-guideline violations None

Test Classification Details

Test File Classification Issues Detected
should resolve temporary ID references in text file contents before GraphQL replay actions/setup/js/push_signed_commits.test.cjs ✅ Design No error/edge cases (minor)

Flagged Tests — Requires Review

No tests failed behavioral classification. One minor observation:

i️ Missing error/edge cases

Test: should resolve temporary ID references in text file contents before GraphQL replay
Observation: The test covers the happy path (valid resolvedTemporaryIds map with a known key) but does not test edge cases such as: an #aw_* reference with no mapping in resolvedTemporaryIds, multiple temporary IDs in a single file, or a commit with binary files containing temporary IDs.
Suggested addition: Add a table-driven scenario or sibling it() block covering the case where resolvedTemporaryIds does not contain a referenced #aw_* ID (i.e., the token should be left unchanged or handled gracefully).


Test Analysis Summary

The single new test verifies the core behavioral contract of the PR: that #aw_* temporary ID tokens in committed file contents are resolved to real GitHub issue URLs and short #N references before the GraphQL signed-commit replay. With 5 assertions checking:

  1. The GraphQL mutation was invoked exactly once
  2. The file additions array contains exactly one entry
  3. The resolved URL form (/issues/66708) appears in the content
  4. The short #66708 reference appears in the content
  5. The original #aw_test1 token does not appear in the content

This is a genuine behavioral contract test — it exercises a real git repository (no git mocking), a mock GitHub API client, and asserts on the observable mutation payload. Deleting this test would allow a regression in temporary-ID resolution to go undetected during signed-commit bundle replay.

Test inflation ratio: The test file gained 32 lines vs. 86 lines added across the production files (push_signed_commits.cjs +82, push_to_pull_request_branch.cjs +2, create_pull_request.cjs +8). Ratio ≈ 0.37:1 — no inflation detected.


Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 0 tests — no Go test files were modified
  • 🟨 JavaScript (*.test.cjs, *.test.js): 1 test (vitest)

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). The single new test directly verifies the observable behavior of the temporary-ID resolution feature.


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

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

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: 82/100. Test quality is excellent — 0% of new tests are implementation tests (threshold: 30%). The single new test directly verifies the behavioral contract of temporary-ID resolution during signed-commit bundle replay.

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.

Skills-Based Review 🧠

Applied /tdd, /diagnose, and /zoom-out — this is a targeted bug fix with regression coverage, so those three lenses are most useful.

Key Themes

  • Test assertion precision — the new test verifies the main scenario but the toContain("#66708") assertion doesn't distinguish URL from text-context replacement; a more specific check would make the test a true specification.
  • Missing edge-case coverage — no test for the backward-compatibility path (resolvedTemporaryIds omitted) or binary/non-UTF-8 files, which are both branches exercised by the new code.
  • Duplicated normalisation logicbuildTemporaryIdMap reimplements the same loop already present in loadTemporaryIdMapFromResolved (exported from temporary_id.cjs); extending the existing function to accept an optional currentRepo would remove the duplication.
  • Naming mismatchreplaceTemporaryIdReferencesInPatch is applied to raw file content, which may confuse future readers about whether patch-specific handling applies.

Positive Highlights

  • ✅ Clean, well-documented helper functions with JSDoc throughout.
  • ✅ The binary-detection heuristic (UTF-8 round-trip check) is a pragmatic approach that avoids corrupting non-text blobs.
  • ✅ All four call sites in create_pull_request.cjs were updated consistently — no partial fix.
  • ✅ The effectiveCurrentRepo fallback (currentRepo || owner/repo) is a nice safety net.

Verdict

The fix is correct and the approach is sound. All comments are non-blocking suggestions — commenting rather than requesting changes.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · ● 6.7M

const additions = githubClient.graphql.mock.calls[0][1].input.fileChanges.additions;
expect(additions).toHaveLength(1);
const resolvedContent = Buffer.from(additions[0].contents, "base64").toString();
expect(resolvedContent).toContain("https://github.com/test-owner/test-repo/issues/66708");
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 assertion toContain("#66708") is too broad — it passes as long as the number appears anywhere in the resolved content, including inside the URL replacement (issues/66708). This means the text-context replacement (// linked: #aw_test1// linked: #66708) is never actually verified independently.

Consider a more specific assertion:

expect(resolvedContent).toContain("// linked: #66708");

This confirms both the URL path (/issues/66708) and the plain #66708 text-context replacements are actually working.

expect(resolvedContent).toContain("https://github.com/test-owner/test-repo/issues/66708");
expect(resolvedContent).toContain("#66708");
expect(resolvedContent).not.toContain("#aw_test1");
});
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] No test covers the backward-compatibility path where resolvedTemporaryIds is undefined (callers that haven't been updated yet). Without it, we can't confirm that omitting the new parameter leaves existing behaviour untouched.

A simple regression guard:

it("should leave file content unchanged when resolvedTemporaryIds is undefined", async () => {
  // ... set up a commit with a regular file ...
  await pushSignedCommits({ githubClient, owner, repo, branch, baseRef, cwd });
  const additions = githubClient.graphql.mock.calls[0][1].input.fileChanges.additions;
  expect(Buffer.from(additions[0].contents, "base64").toString()).toBe("expected original content");
});

@@ -124,6 +126,74 @@ async function readBlobAsBase64(blobHash, cwd) {
return Buffer.concat(chunks).toString("base64");
}
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.

[/zoom-out] buildTemporaryIdMap is almost identical to the already-exported loadTemporaryIdMapFromResolved in temporary_id.cjs. The only meaningful difference is that loadTemporaryIdMapFromResolved falls back to the GitHub Actions context.repo while this function accepts an explicit currentRepo string.

Rather than maintaining two copies of the same normalisation loop, consider exporting a variant (or extending the existing one with an optional currentRepo parameter) so this file can delegate:

const { loadTemporaryIdMapFromResolved } = require("./temporary_id.cjs");
// ... pass currentRepo explicitly rather than relying on context

Duplicated logic here will silently diverge if loadTemporaryIdMapFromResolved is ever updated.

*
* @param {string} base64Content
* @param {Map<string, {repo: string, number: number}>} temporaryIdMap
* @param {string} currentRepo
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.

[/diagnose] The function replaceTemporaryIdReferencesInPatch is named for patch/diff text, but here it's applied to raw file contents. The function happens to work correctly (it does two-pass URL + text replacement), but the name creates a misleading mental model for future readers who may wonder whether patch-specific heuristics (like +/- diff markers) affect the behaviour.

A renaming like replaceTemporaryIdReferencesInText — or a thin, clearly-named wrapper in this file — would make the intent unambiguous.

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot review all comments

Copilot AI and others added 2 commits May 19, 2026 01:45
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 19, 2026

@copilot review all comments

Addressed actionable review feedback in commit 4c7dfa5.

  • Added strict positive-integer validation for resolved temporary ID numbers in buildTemporaryIdMap (invalid entries are ignored with warning).
  • Broadened the early content precheck to detect #aw_ candidates so malformed references still go through replacement/warning logic.
  • Added regression tests for malformed-candidate warning path and invalid-number handling (preventing #NaN replacements).

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 remove duplicate, format, lint


const { ERR_API } = require("./error_codes.cjs");
const { normalizeTemporaryId, replaceTemporaryIdReferencesInPatch } = require("./temporary_id.cjs");
const TEMPORARY_ID_REFERENCE_PATTERN = /#aw_[A-Za-z0-9_]{3,12}\b/i;
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 move to temporaryid 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.

Moved in 86b539e: the candidate precheck regex now lives in temporary_id.cjs and push_signed_commits.cjs imports it from there.


const { ERR_API } = require("./error_codes.cjs");
const { normalizeTemporaryId, replaceTemporaryIdReferencesInPatch } = require("./temporary_id.cjs");
const TEMPORARY_ID_REFERENCE_PATTERN = /#aw_[A-Za-z0-9_]{3,12}\b/i;
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 move to temporaryid 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.

Addressed in 86b539e: regex precheck was moved to temporary_id.cjs and reused from push_signed_commits.cjs.

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

Copilot AI commented May 19, 2026

@copilot remove duplicate, format, lint

Done in 86b539e: removed the duplicate precheck constant by moving it into temporary_id.cjs, then ran formatting and lint checks plus targeted tests (push_signed_commits.test.cjs).

Copilot AI requested a review from pelikhan May 19, 2026 02:34
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 review all review comments

* @param {string} currentRepo
* @returns {Map<string, {repo: string, number: number}>}
*/
function buildTemporaryIdMap(resolvedTemporaryIds, currentRepo) {
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 review if this function is a duplicate and 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 ccca10a: removed the local duplicate buildTemporaryIdMap from push_signed_commits.cjs and now reuse loadTemporaryIdMapFromResolved from temporary_id.cjs with signed-replay-specific validation options.

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

Copilot AI commented May 19, 2026

@copilot review all review comments

Reviewed and addressed the open review feedback in ccca10a by deduplicating the temporary-ID map builder and reusing the shared helper with the existing signed-replay validation/warning behavior.

Copilot AI and others added 3 commits May 19, 2026 02:59
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI requested a review from pelikhan May 19, 2026 03:02
@pelikhan pelikhan merged commit e619b76 into main May 19, 2026
@pelikhan pelikhan deleted the copilot/fix-bundle-push-path-issue branch May 19, 2026 04:07
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.

Bundle-based push path skips temporary ID substitution in file contents

3 participants