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
46 changes: 46 additions & 0 deletions .github/actions/setup-api-client/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,46 @@ runs:

create_vendor_aliases

# Strip unresolvable file: dependencies from package.json so npm install
# doesn't choke when vendored packages are missing (e.g. sparse checkout
# that doesn't include node_modules/). Back up the original first so we
# can restore it after npm install — avoids leaking modified state into
# downstream steps that expect a clean workspace.
STRIPPED_PKG_JSON=false
if [ -f "package.json" ]; then
node -e '
const fs = require("fs");
const path = require("path");
const raw = fs.readFileSync("package.json", "utf8");
const pkg = JSON.parse(raw);
let changed = false;
for (const section of ["dependencies", "devDependencies"]) {
if (!pkg[section]) continue;
for (const [name, spec] of Object.entries(pkg[section])) {
if (typeof spec === "string" && spec.startsWith("file:")) {
const target = path.resolve(spec.slice(5));
if (!fs.existsSync(target)) {
delete pkg[section][name];
changed = true;
console.log("::notice::Stripped unresolvable vendored dep: " + name + " (" + spec + ")");
}
}
}
if (pkg[section] && Object.keys(pkg[section]).length === 0) {
delete pkg[section];
}
}
if (changed) {
fs.copyFileSync("package.json", "package.json.bak");
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 2) + "\n");
process.stdout.write("STRIPPED");
}
'
if [ -f "package.json.bak" ]; then
STRIPPED_PKG_JSON=true
fi
fi

# Vendored packages often ship lifecycle scripts (tshy builds, custom bundlers, etc.)
# that expect repo-specific tooling. Disable lifecycle scripts globally so npm install
# never invokes those hooks inside CI.
Expand Down Expand Up @@ -240,6 +280,12 @@ runs:
echo "✅ @octokit dependencies installed"
fi

# Restore original package.json if we stripped file: deps earlier,
# so downstream steps see an unmodified workspace.
if [ "$STRIPPED_PKG_JSON" = "true" ] && [ -f "package.json.bak" ]; then
mv "package.json.bak" "package.json"
fi

cleanup_vendor_aliases
trap - EXIT
- name: Export NODE_PATH for shared deps
Expand Down
249 changes: 237 additions & 12 deletions .github/workflows/agents-auto-pilot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,14 @@ jobs:
const hasFormat = labels.includes('agents:formatted');
const hasOptimize = labels.includes('agents:optimize');
const hasApplySuggestions = labels.includes('agents:apply-suggestions');
const nonRoutingAgentLabels = new Set(['agent:rate-limited', 'agent:needs-attention', 'agent:retry']);
const hasAgentLabel = labels.some(l => l.startsWith('agent:') && !nonRoutingAgentLabels.has(l));
const nonRoutingAgentLabels = new Set([
'agent:rate-limited',
'agent:needs-attention',
'agent:retry',
]);
const hasAgentLabel = labels.some(
(label) => label.startsWith('agent:') && !nonRoutingAgentLabels.has(label),
);
const hasAutofix = labels.includes('autofix');
const hasAutomerge = labels.includes('automerge');
const hasVerify = labels.includes('verify:evaluate');
Expand Down Expand Up @@ -1707,16 +1713,142 @@ jobs:
const issueNumber = parseInt(process.env.ISSUE_NUMBER);
const stepCount = parseInt(process.env.STEP_COUNT || '0') + 1;

const toLabelName = (label) => {
if (!label) return '';
if (typeof label === 'string') return label;
return label.name || '';
};

const loadIssueLabels = async () => {
const payload = context.payload || {};
if (
payload.issue &&
Number(payload.issue.number) === issueNumber &&
Array.isArray(payload.issue.labels)
) {
return payload.issue.labels;
}
if (
payload.pull_request &&
Number(payload.pull_request.number) === issueNumber &&
Array.isArray(payload.pull_request.labels)
) {
return payload.pull_request.labels;
}
try {
const { data: issue } = await withRetry((client) =>
client.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber
})
);
return issue.labels || [];
} catch (fetchError) {
const warnContext = fetchError?.message || fetchError;
core.warning(
'Failed to load issue labels before capability check: ' + warnContext,
);
return [];
}
};

let issueLabels = await loadIssueLabels();
let agentKey = 'codex';
let defaultAgent = agentKey;
let registry = null;
let recognizedAgentLabels = [];
const runnerOverride = issueLabels
.map((label) => toLabelName(label).toLowerCase())
.find((name) => name.startsWith('runner:'));

try {
const { loadAgentRegistry } = require('./.github/scripts/agent_registry.js');
const registry = loadAgentRegistry();
const defaultAgent = registry.default_agent || agentKey;
agentKey = String(defaultAgent).trim().toLowerCase() || agentKey;
} catch (error) {
const prefix = `Failed to load agent registry; defaulting to ${agentKey}:`;
core.warning(`${prefix} ${error.message}`);
const registryLib = require('./.github/scripts/agent_registry.js');
const { loadAgentRegistry, resolveAgentFromLabels } = registryLib;
registry = loadAgentRegistry();
defaultAgent =
String(registry.default_agent || agentKey).trim().toLowerCase() || agentKey;

if (Array.isArray(issueLabels) && issueLabels.length) {
const knownAgents = new Set(Object.keys(registry.agents || {}));
recognizedAgentLabels = issueLabels
.map((label) => {
const normalized = toLabelName(label).toLowerCase();
const display = typeof label === 'string' ? label : (label?.name || normalized);
return { normalized, display };
})
.filter(({ normalized }) => normalized.startsWith('agent:'))
.filter(({ normalized }) => {
const suffix = normalized.slice('agent:'.length);
if (suffix === 'auto') {
return true;
}
return knownAgents.has(suffix);
});

const routingLabels = recognizedAgentLabels.length
? recognizedAgentLabels.map(({ normalized }) => normalized)
: issueLabels;

if (runnerOverride) {
const runnerKey = runnerOverride.slice('runner:'.length).trim();
agentKey = runnerKey || defaultAgent;
} else if (routingLabels.length) {
try {
agentKey =
resolveAgentFromLabels(
routingLabels,
{ registryPath: './.github/agents/registry.yml' },
) || defaultAgent;
} catch (resolveError) {
const resolveContext = resolveError?.message || resolveError;
core.warning(
'Failed to resolve agent label; defaulting to ' +
defaultAgent +
': ' +
resolveContext,
);
agentKey = defaultAgent;
}
} else {
agentKey = defaultAgent;
}
} else if (runnerOverride) {
const runnerKey = runnerOverride.slice('runner:'.length).trim();
agentKey = runnerKey || defaultAgent;
} else {
agentKey = defaultAgent;
}
} catch (error) {
const prefix = `Failed to load agent registry; defaulting to ${agentKey}:`;
core.warning(`${prefix} ${error.message}`);
}

if (registry && recognizedAgentLabels.length) {
const removalTargets = new Set(
recognizedAgentLabels
.filter(({ normalized }) => normalized !== `agent:${agentKey}`)
.map(({ display, normalized }) => (display || normalized).trim())
.filter(Boolean)
);
for (const labelName of removalTargets) {
try {
await withRetry((client) =>
client.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
name: labelName
})
);
} catch (removeError) {
const removeContext = removeError?.message || removeError;
core.warning(
`Failed to remove label ${labelName}: ${removeContext}`,
);
}
}
}

await withRetry((client) => client.rest.issues.createComment({
owner: context.repo.owner,
Expand Down Expand Up @@ -1969,13 +2101,106 @@ jobs:
const issueTitle = process.env.ISSUE_TITLE || `Issue #${issueNumber}`;
const stepCount = parseInt(process.env.STEP_COUNT || '0') + 1;
let agentKey = 'codex';
let issueLabels = [];
let branchPrefix = 'codex/issue-';

const toLabelName = (label) => {
if (!label) return '';
if (typeof label === 'string') return label;
return label.name || '';
};

const loadIssueLabels = async () => {
const payload = context.payload || {};
if (
payload.issue &&
Number(payload.issue.number) === issueNumber &&
Array.isArray(payload.issue.labels)
) {
return payload.issue.labels;
}
if (
payload.pull_request &&
Number(payload.pull_request.number) === issueNumber &&
Array.isArray(payload.pull_request.labels)
) {
return payload.pull_request.labels;
}
try {
const { data: issue } = await withRetry((client) =>
client.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber
})
);
return issue.labels || [];
} catch (fetchError) {
const warnContext = fetchError?.message || fetchError;
core.warning(
'Failed to load issue labels before create-pr: ' + warnContext,
);
return [];
}
};

issueLabels = await loadIssueLabels();

try {
const registryLib = require('./.github/scripts/agent_registry.js');
const { loadAgentRegistry, getAgentConfig } = registryLib;
const { loadAgentRegistry, getAgentConfig, resolveAgentFromLabels } = registryLib;
const registry = loadAgentRegistry();
const defaultAgent = registry.default_agent || agentKey;
agentKey = String(defaultAgent).trim().toLowerCase() || agentKey;
const defaultAgent =
String(registry.default_agent || agentKey).trim().toLowerCase() || agentKey;
const runnerOverride = issueLabels
.map((label) => toLabelName(label).toLowerCase())
.find((name) => name.startsWith('runner:'));

let recognizedAgentLabels = [];
if (Array.isArray(issueLabels) && issueLabels.length) {
const knownAgents = new Set(Object.keys(registry.agents || {}));
recognizedAgentLabels = issueLabels
.map((label) => {
const normalized = toLabelName(label).toLowerCase();
return normalized;
})
.filter((normalized) => normalized.startsWith('agent:'))
.filter((normalized) => {
const suffix = normalized.slice('agent:'.length);
if (suffix === 'auto') {
return true;
}
return knownAgents.has(suffix);
});
}

const routingLabels = recognizedAgentLabels.length
? recognizedAgentLabels
: issueLabels;

if (runnerOverride) {
const runnerKey = runnerOverride.slice('runner:'.length).trim();
agentKey = runnerKey || defaultAgent;
} else if (routingLabels.length) {
try {
agentKey =
resolveAgentFromLabels(
routingLabels,
{ registryPath: './.github/agents/registry.yml' },
) || defaultAgent;
} catch (resolveError) {
const resolveContext = resolveError?.message || resolveError;
core.warning(
'Failed to resolve agent label; defaulting to ' +
defaultAgent +
': ' +
resolveContext,
);
agentKey = defaultAgent;
}
} else {
agentKey = defaultAgent;
}
const cfg = getAgentConfig(agentKey);
branchPrefix = String(cfg.branch_prefix || branchPrefix);
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/agents-verify-to-issue-v2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ jobs:
.github/scripts/token_load_balancer.js
scripts/langchain
tools
sparse-checkout-cone-mode: false

- name: Setup API client
uses: ./.github/actions/setup-api-client
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/agents-verify-to-new-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ jobs:
.github/scripts/token_load_balancer.js
scripts/langchain
tools
sparse-checkout-cone-mode: false

- name: Setup API client
uses: ./.github/actions/setup-api-client
Expand Down
29 changes: 29 additions & 0 deletions agents/codex-1638.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!--
needs-human:
Label: needs-human
Blocked by workflow protection: the failing step is in protected workflow files (`.github/workflows/**`), which cannot be edited in `agent-standard`.

Failing Gate run:
- Run: `22291979366`
- PR: `#1638`
- Job: `python ci / lint-ruff`
- Step: `Install uv`

Observed root cause:
- The `lint-ruff` job installs uv via:
- `curl -LsSf https://astral.sh/uv/install.sh | sh`
- This external installer call failed before lint execution, so Gate summary failed at `Enforce Gate success`.

Required workflow fix (agent-high-privilege):
1. Update uv installation in `.github/workflows/reusable-10-ci-python.yml` (at least the `lint-ruff` job block around `Install uv`) to avoid single-point failure from the remote installer.
2. Suggested minimal hardened install logic:
- Try `uv --version` first; if present, skip install.
- Otherwise run `curl -LsSf https://astral.sh/uv/install.sh | sh`.
- If curl install fails, fall back to `python -m pip install --user uv`.
- Append `"$HOME/.local/bin"` to `GITHUB_PATH`.
3. Mirror the same hardening in other duplicated `Install uv` blocks in the same workflow to prevent recurring failures.

Verification after workflow patch:
- Re-run Gate on PR #1638.
- Expect `python ci / lint-ruff` to pass `Install uv` and proceed to Ruff checks.
-->
Loading
Loading