Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
199f1b7
Add merge-pull-request safe-output config and runtime handler scaffol…
Copilot Apr 19, 2026
60c3d24
Fix merge_pull_request handler type-safe success path
Copilot Apr 19, 2026
af33a7b
Add retry and extensive logging to merge_pull_request handler
Copilot Apr 19, 2026
355be65
Refine merge_pull_request retry logic and diagnostic logging
Copilot Apr 19, 2026
1dbc356
Block merge_pull_request on repository default branch
Copilot Apr 19, 2026
a194986
Simplify default branch detection in merge gate
Copilot Apr 19, 2026
d4e226e
Retry-wrap GraphQL review summary calls
Copilot Apr 19, 2026
d9ccab5
Update safe-outputs specification for merge_pull_request
Copilot Apr 19, 2026
b9ecfc7
Add spec enforcement test for merge_pull_request
Copilot Apr 19, 2026
0a74f69
docs(adr): add draft ADR-27193 for gated merge-pull-request safe-output
github-actions[bot] Apr 19, 2026
b996f24
Enforce protected base branch checks and branch name sanitization
Copilot Apr 19, 2026
f663f8b
Treat merge allowed-labels as exact labels not globs
Copilot Apr 19, 2026
e675d67
Expand exact-label matching tests for merge_pull_request
Copilot Apr 19, 2026
80d176b
Add tests for merge pull request label validation
Copilot Apr 19, 2026
47b7e59
Refuse default-branch merges in spec tests and handle temporary IDs
Copilot Apr 19, 2026
856b3cc
Polish merge_pull_request docs and test naming
Copilot Apr 19, 2026
7f6709d
Improve merge PR temp-id resolution messaging and tests
Copilot Apr 19, 2026
902c7c3
Merge remote-tracking branch 'origin/main' into copilot/add-merge-pul…
Copilot Apr 20, 2026
ea2daf4
Merge main and recompile workflow lock files
Copilot Apr 20, 2026
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
1 change: 1 addition & 0 deletions .github/workflows/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"mempalace==3.2.0"
markitdown-mcp
numpy
scikit-learn
52 changes: 26 additions & 26 deletions .github/workflows/smoke-create-cross-repo-pr.lock.yml

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions .github/workflows/smoke-create-cross-repo-pr.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: Smoke Create Cross-Repo PR
description: Smoke test validating cross-repo pull request creation in githubnext/gh-aw-side-repo
description: Smoke test validating cross-repo pull request creation in github/gh-aw-side-repo
on:
workflow_dispatch:
pull_request:
Expand All @@ -19,7 +19,7 @@ network:
- github

checkout:
- repository: githubnext/gh-aw-side-repo
- repository: github/gh-aw-side-repo
github-token: ${{ secrets.GH_AW_SIDE_REPO_PAT }}

tools:
Expand All @@ -33,7 +33,7 @@ tools:
safe-outputs:
allowed-domains: [default-safe-outputs]
create-pull-request:
target-repo: "githubnext/gh-aw-side-repo"
target-repo: "github/gh-aw-side-repo"
github-token: ${{ secrets.GH_AW_SIDE_REPO_PAT }}
title-prefix: "[smoke] "
labels: [smoke-test]
Expand All @@ -50,8 +50,8 @@ safe-outputs:
max: 2
messages:
footer: "> 🔬 *Cross-repo smoke test by [{workflow_name}]({run_url})*{effective_tokens_suffix}{history_link}"
run-started: "🔬 [{workflow_name}]({run_url}) is testing cross-repo PR creation in githubnext/gh-aw-side-repo..."
run-success: "✅ [{workflow_name}]({run_url}) successfully created a cross-repo PR in githubnext/gh-aw-side-repo!"
run-started: "🔬 [{workflow_name}]({run_url}) is testing cross-repo PR creation in github/gh-aw-side-repo..."
run-success: "✅ [{workflow_name}]({run_url}) successfully created a cross-repo PR in github/gh-aw-side-repo!"
run-failure: "❌ [{workflow_name}]({run_url}) failed to create a cross-repo PR: {status}"

timeout-minutes: 10
Expand Down
50 changes: 25 additions & 25 deletions .github/workflows/smoke-update-cross-repo-pr.lock.yml

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions .github/workflows/smoke-update-cross-repo-pr.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: Smoke Update Cross-Repo PR
description: Smoke test validating cross-repo pull request updates in githubnext/gh-aw-side-repo by adding lines from Homer's Odyssey to the README
description: Smoke test validating cross-repo pull request updates in github/gh-aw-side-repo by adding lines from Homer's Odyssey to the README

on:
workflow_dispatch:
Expand All @@ -20,7 +20,7 @@ network:
- github

checkout:
- repository: githubnext/gh-aw-side-repo
- repository: github/gh-aw-side-repo
github-token: ${{ secrets.GH_AW_SIDE_REPO_PAT }}
fetch: ["main", "refs/pulls/open/*"] # fetch all open PR refs after checkout
fetch-depth: 0 # fetch full history to ensure we can see all commits and PR details
Expand All @@ -44,13 +44,13 @@ safe-outputs:
hide-older-comments: true
max: 2
push-to-pull-request-branch:
target-repo: "githubnext/gh-aw-side-repo"
target-repo: "github/gh-aw-side-repo"
github-token: ${{ secrets.GH_AW_SIDE_REPO_PAT }}
if-no-changes: "error"
target: "1" # PR #1
messages:
footer: "> 📜 *Cross-repo PR update smoke test by [{workflow_name}]({run_url})*{effective_tokens_suffix}{history_link}"
run-started: "📜 [{workflow_name}]({run_url}) is adding the next Odyssey line to githubnext/gh-aw-side-repo PR #1..."
run-started: "📜 [{workflow_name}]({run_url}) is adding the next Odyssey line to github/gh-aw-side-repo PR #1..."
run-success: "✅ [{workflow_name}]({run_url}) successfully updated the cross-repo PR with a new Odyssey line!"
run-failure: "❌ [{workflow_name}]({run_url}) failed to update the cross-repo PR: {status}"

Expand Down
83 changes: 83 additions & 0 deletions actions/setup/js/check_runs_helpers.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// @ts-check

/**
* Returns true for check runs that represent deployment environment gates rather
* than CI checks.
* @param {any} run
* @returns {boolean}
*/
function isDeploymentCheck(run) {
return run?.app?.slug === "github-deployments";
}

/**
* Select latest check run per name and apply standard filtering.
* @param {any[]} checkRuns
* @param {{
* includeList?: string[]|null,
* excludeList?: string[]|null,
* excludedCheckRunIds?: Set<number>,
* }} [options]
* @returns {{relevant: any[], deploymentCheckCount: number, currentRunFilterCount: number}}
*/
function selectLatestRelevantChecks(checkRuns, options = {}) {
const includeList = options.includeList || null;
const excludeList = options.excludeList || null;
const excludedCheckRunIds = options.excludedCheckRunIds || new Set();

/** @type {Map<string, any>} */
const latestByName = new Map();
let deploymentCheckCount = 0;
let currentRunFilterCount = 0;

for (const run of checkRuns) {
if (isDeploymentCheck(run)) {
deploymentCheckCount++;
continue;
}
if (excludedCheckRunIds.has(run.id)) {
currentRunFilterCount++;
continue;
}
const existing = latestByName.get(run.name);
if (!existing || new Date(run.started_at ?? 0) > new Date(existing.started_at ?? 0)) {
latestByName.set(run.name, run);
}
}

const relevant = [];
for (const [name, run] of latestByName) {
if (includeList && includeList.length > 0 && !includeList.includes(name)) {
continue;
}
if (excludeList && excludeList.length > 0 && excludeList.includes(name)) {
continue;
}
relevant.push(run);
}

return { relevant, deploymentCheckCount, currentRunFilterCount };
}

/**
* Computes failing checks with shared semantics.
* @param {any[]} checkRuns
* @param {{allowPending?: boolean}} [options]
* @returns {any[]}
*/
function getFailingChecks(checkRuns, options = {}) {
const allowPending = options.allowPending === true;
const failedConclusions = new Set(["failure", "cancelled", "timed_out"]);
return checkRuns.filter(run => {
if (run.status === "completed") {
return run.conclusion != null && failedConclusions.has(run.conclusion);
}
return !allowPending;
});
}

module.exports = {
isDeploymentCheck,
selectLatestRelevantChecks,
getFailingChecks,
};
66 changes: 7 additions & 59 deletions actions/setup/js/check_skip_if_check_failing.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { getErrorMessage, isRateLimitError } = require("./error_helpers.cjs");
const { ERR_API } = require("./error_codes.cjs");
const { getBaseBranch } = require("./get_base_branch.cjs");
const { writeDenialSummary } = require("./pre_activation_summary.cjs");
const { selectLatestRelevantChecks, getFailingChecks } = require("./check_runs_helpers.cjs");

/**
* Determines the ref to check for CI status.
Expand Down Expand Up @@ -52,22 +53,6 @@ function parseListEnv(envValue) {
}
}

/**
* Returns true for check runs that represent deployment environment gates rather
* than CI checks. These should be ignored by default so that a pending deployment
* approval does not falsely block the agentic workflow.
*
* Deployment gate checks are identified by the GitHub App that created them:
* - "github-deployments" – the built-in GitHub Deployments service
*
* @param {object} run - A check run object from the GitHub API
* @returns {boolean}
*/
function isDeploymentCheck(run) {
const slug = run.app?.slug;
return slug === "github-deployments";
}

/**
* Fetches the check run IDs for all jobs in the current workflow run.
* These IDs are used to filter out the current workflow's own checks
Expand Down Expand Up @@ -149,25 +134,11 @@ async function main() {
// Filter to the latest run per check name (GitHub may have multiple runs per name).
// Deployment gate checks and the current run's own checks are silently skipped here
// so they never influence the gate.
/** @type {Map<string, object>} */
const latestByName = new Map();
let deploymentCheckCount = 0;
let currentRunFilterCount = 0;
for (const run of checkRuns) {
if (isDeploymentCheck(run)) {
deploymentCheckCount++;
continue;
}
if (currentRunCheckRunIds.has(run.id)) {
currentRunFilterCount++;
continue;
}
const name = run.name;
const existing = latestByName.get(name);
if (!existing || new Date(run.started_at ?? 0) > new Date(existing.started_at ?? 0)) {
latestByName.set(name, run);
}
}
const { relevant, deploymentCheckCount, currentRunFilterCount } = selectLatestRelevantChecks(checkRuns, {
includeList,
excludeList,
excludedCheckRunIds: currentRunCheckRunIds,
});

if (deploymentCheckCount > 0) {
core.info(`Skipping ${deploymentCheckCount} deployment gate check(s) (app: github-deployments)`);
Expand All @@ -176,32 +147,9 @@ async function main() {
core.info(`Skipping ${currentRunFilterCount} check run(s) from the current workflow run`);
}

// Apply user-defined include/exclude filtering
const relevant = [];
for (const [name, run] of latestByName) {
if (includeList && includeList.length > 0 && !includeList.includes(name)) {
continue;
}
if (excludeList && excludeList.length > 0 && excludeList.includes(name)) {
continue;
}
relevant.push(run);
}

core.info(`Evaluating ${relevant.length} check run(s) after filtering`);

// A check is "failing" if it either:
// 1. Completed with a non-success conclusion (failure, cancelled, timed_out), OR
// 2. Is still pending/in-progress — unless allow-pending is set
const failedConclusions = new Set(["failure", "cancelled", "timed_out"]);

const failingChecks = relevant.filter(run => {
if (run.status === "completed") {
return run.conclusion != null && failedConclusions.has(run.conclusion);
}
// Pending/queued/in_progress: treat as failing unless allow-pending is true
return !allowPending;
});
const failingChecks = getFailingChecks(relevant, { allowPending });

if (failingChecks.length > 0) {
const names = failingChecks.map(r => (r.status === "completed" ? `${r.name} (${r.conclusion})` : `${r.name} (${r.status})`)).join(", ");
Expand Down
Loading