Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions .github/workflows/design-decision-gate.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .github/workflows/design-decision-gate.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ safe-outputs:
allowed-files:
- docs/adr/**
patch-format: bundle
ignore-missing-branch-failure: true
commit-title-suffix: " [design-decision-gate]"
noop:
messages:
Expand Down
43 changes: 42 additions & 1 deletion actions/setup/js/push_to_pull_request_branch.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@ const { getGitAuthEnv } = require("./git_helpers.cjs");

/** @type {string} Safe output type handled by this module */
const HANDLER_TYPE = "push_to_pull_request_branch";
const MISSING_BRANCH_ERROR_TEMPLATE = branchName => `Branch ${branchName} no longer exists on origin (it may have been deleted), can't push to it.`;
const MISSING_REMOTE_REF_PATTERNS = [
"couldn't find remote ref",
"could not find remote ref",
"remote ref does not exist",
"did not match any file(s) known to git",
"unknown revision or path not in the working tree",
"fatal: couldn't find remote ref",
"exit code 128",
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

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

looksLikeMissingRemoteBranchError treats any error text containing "exit code 128" as a missing-branch signal. Exit code 128 is a generic git failure (auth, network, repo state, etc.), so with ignore_missing_branch_failure enabled this can incorrectly mark real failures as skipped. Remove this pattern (or replace it with more specific stderr substrings tied to missing refs) so only true missing-branch cases are skipped.

This issue also appears on line 534 of the same file.

Suggested change
"exit code 128",

Copilot uses AI. Check for mistakes.
];

/**
* @param {unknown} value
* @returns {boolean}
*/
function looksLikeMissingRemoteBranchError(value) {
const text = String(value ?? "").toLowerCase();
return MISSING_REMOTE_REF_PATTERNS.some(pattern => text.includes(pattern));
}

/**
* Main handler factory for push_to_pull_request_branch
Expand All @@ -36,6 +55,7 @@ async function main(config = {}) {
const titlePrefix = config.title_prefix || "";
const envLabels = config.labels ? (Array.isArray(config.labels) ? config.labels : config.labels.split(",")).map(label => String(label).trim()).filter(label => label) : [];
const ifNoChanges = config.if_no_changes || "warn";
const ignoreMissingBranchFailure = config.ignore_missing_branch_failure === true;
const commitTitleSuffix = config.commit_title_suffix || "";
const maxSizeKb = config.max_patch_size ? parseInt(String(config.max_patch_size), 10) : 1024;
const maxCount = config.max || 0; // 0 means no limit
Expand Down Expand Up @@ -70,6 +90,7 @@ async function main(config = {}) {
core.info(`Required labels: ${envLabels.join(", ")}`);
}
core.info(`If no changes: ${ifNoChanges}`);
core.info(`Ignore missing branch failure: ${ignoreMissingBranchFailure}`);
if (commitTitleSuffix) {
core.info(`Commit title suffix: ${commitTitleSuffix}`);
}
Expand Down Expand Up @@ -464,9 +485,18 @@ async function main(config = {}) {
});

if (lsRemoteResult.exitCode === 2) {
const missingBranchError = MISSING_BRANCH_ERROR_TEMPLATE(branchName);
if (ignoreMissingBranchFailure) {
core.warning(`${missingBranchError} Skipping as configured by ignore-missing-branch-failure.`);
return {
success: false,
error: missingBranchError,
skipped: true,
};
}
return {
success: false,
error: `Branch ${branchName} no longer exists on origin (it may have been deleted), can't push to it.`,
error: missingBranchError,
};
}

Expand All @@ -488,13 +518,24 @@ async function main(config = {}) {
env: { ...process.env, ...gitAuthEnv },
});
} catch (fetchError) {
const fetchErrorMessage = fetchError instanceof Error ? fetchError.message : String(fetchError);
if (ignoreMissingBranchFailure && looksLikeMissingRemoteBranchError(fetchErrorMessage)) {
const missingBranchError = MISSING_BRANCH_ERROR_TEMPLATE(branchName);
core.warning(`${missingBranchError} Skipping as configured by ignore-missing-branch-failure.`);
return { success: false, error: missingBranchError, skipped: true };
}
return { success: false, error: `Failed to fetch branch ${branchName}: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}` };
}

// Check if branch exists on origin
try {
await exec.exec(`git rev-parse --verify origin/${branchName}`);
} catch (verifyError) {
const missingBranchError = MISSING_BRANCH_ERROR_TEMPLATE(branchName);
if (ignoreMissingBranchFailure) {
core.warning(`${missingBranchError} Skipping as configured by ignore-missing-branch-failure.`);
return { success: false, error: missingBranchError, skipped: true };
}
return { success: false, error: `Branch ${branchName} does not exist on origin, can't push to it: ${verifyError instanceof Error ? verifyError.message : String(verifyError)}` };
}

Expand Down
33 changes: 33 additions & 0 deletions actions/setup/js/push_to_pull_request_branch.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,22 @@ index 0000000..abc1234
expect(mockExec.exec).not.toHaveBeenCalled();
});

it("should skip deleted branch failure when ignore_missing_branch_failure is enabled", async () => {
const patchPath = createPatchFile();

mockExec.getExecOutput.mockResolvedValueOnce({ exitCode: 2, stdout: "", stderr: "fatal: couldn't find remote ref feature-branch" });

const module = await loadModule();
const handler = await module.main({ ignore_missing_branch_failure: true });
const result = await handler({ patch_path: patchPath }, {});

expect(result.success).toBe(false);
expect(result.skipped).toBe(true);
expect(result.error).toContain("no longer exists on origin");
expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("ignore-missing-branch-failure"));
expect(mockExec.exec).not.toHaveBeenCalled();
});

it("should fail with diagnostic error when branch existence check fails for other reasons", async () => {
const patchPath = createPatchFile();

Expand Down Expand Up @@ -689,6 +705,23 @@ index 0000000..abc1234
expect(result.error).toContain("does not exist on origin");
});

it("should skip rev-parse missing branch failure when ignore_missing_branch_failure is enabled", async () => {
const patchPath = createPatchFile();

// git fetch succeeds, but git rev-parse fails
mockExec.exec.mockResolvedValueOnce(0); // fetch
mockExec.exec.mockRejectedValueOnce(new Error("fatal: Needed a single revision"));

const module = await loadModule();
const handler = await module.main({ ignore_missing_branch_failure: true });
const result = await handler({ patch_path: patchPath }, {});

expect(result.success).toBe(false);
expect(result.skipped).toBe(true);
expect(result.error).toContain("no longer exists on origin");
expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("ignore-missing-branch-failure"));
});

it("should handle git checkout failure", async () => {
const patchPath = createPatchFile();

Expand Down
5 changes: 5 additions & 0 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -7074,6 +7074,11 @@
"enum": ["warn", "error", "ignore"],
"description": "Behavior when no changes to push: 'warn' (default - log warning but succeed), 'error' (fail the action), or 'ignore' (silent success)"
},
"ignore-missing-branch-failure": {
"type": "boolean",
"description": "When true, treat deleted/missing pull request branch errors as a skipped push instead of a hard failure. Useful when the PR branch may be deleted before safe outputs run.",
"default": false
},
"commit-title-suffix": {
"type": "string",
"description": "Optional suffix to append to generated commit titles (e.g., ' [skip ci]' to prevent triggering CI on the commit)"
Expand Down
1 change: 1 addition & 0 deletions pkg/workflow/compiler_safe_outputs_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ var handlerRegistry = map[string]handlerBuilder{
AddIfNotEmpty("title_prefix", c.TitlePrefix).
AddStringSlice("labels", c.Labels).
AddIfNotEmpty("if_no_changes", c.IfNoChanges).
AddIfTrue("ignore_missing_branch_failure", c.IgnoreMissingBranchFailure).
AddIfNotEmpty("commit_title_suffix", c.CommitTitleSuffix).
AddDefault("max_patch_size", maxPatchSize).
AddIfNotEmpty("target-repo", c.TargetRepoSlug).
Expand Down
Loading
Loading