Skip to content
Closed
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
187 changes: 149 additions & 38 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,43 @@ jobs:
return String(value || '').toLowerCase()
.replace(/[^a-z0-9 ]/g, '').trim();
}
function carriesLabel(source, accepted) {
return (source || []).map(label =>
typeof label === 'string' ? label : label.name
).map(normaliseLabel).some(label =>
accepted.includes(label)
);
}
function declaresAgentContract(issue) {
function section(body, headings) {
const wanted = new Set(headings.map(normaliseLabel));
const lines = String(body || '').split(/\r?\n/);
const output = [];
let collecting = false;
for (const line of lines) {
const heading = line.match(/^#{2,6}\s+(.+?)\s*$/);
if (heading) {
if (collecting) {
break;
}
collecting = wanted.has(normaliseLabel(heading[1]));
continue;
}
if (collecting) {
output.push(line);
}
}
return output.join('\n').trim();
}
function declared(headings) {
const value = section(
String((issue && issue.body) || ''), headings
).replace(/^\x60|\x60$/g, '').trim();
return Boolean(value) && value !== '_No response_';
}
return declared(['agent run id', 'run id']) &&
declared(['agent login']);
}
const login = String(
pull && pull.user && pull.user.login || ''
);
Expand All @@ -628,21 +665,6 @@ jobs:
'openai-codex[bot]',
'chatgpt-codex-connector[bot]'
]);
const labels = [
...((pull && pull.labels) || []).map(label =>
typeof label === 'string' ? label : label.name
),
...(selectedIssue && selectedIssue.labels
? Array.isArray(selectedIssue.labels)
? selectedIssue.labels
: selectedIssue.labels.nodes || []
: []).map(label =>
typeof label === 'string' ? label : label.name
)
].map(normaliseLabel);
const agentLabel = labels.some(label =>
['agent', 'agenttask', 'mcpagent'].includes(label)
);
const agentBranch =
/^(?:agent|claude|codex|copilot|jules)[/-]/i.test(
String(pull && pull.head && pull.head.ref || '')
Expand All @@ -651,10 +673,33 @@ jobs:
/<!--\s*agent-lock-manifest\s*[\s\S]*?-->/i.test(
String(pull && pull.body || '')
);
return login !== 'dependabot[bot]' && (
knownAgents.has(login) || agentBranch || agentLabel ||
manifestPresent
);
// Provenance asserted by the pull request itself. Each of these
// is a claim by the producing side that this is agent work.
const pullProvenance = knownAgents.has(login) || agentBranch ||
manifestPresent ||
carriesLabel((pull && pull.labels) || [],
['agent', 'agenttask', 'mcpagent']);
// Issue-side dispatch. Label automation also applies agent task
// labels as topic tags to issues that never declared a contract,
// so the bare label is not evidence of a dispatch: it only
// counts when the issue actually declares the run id and login
// the gate goes on to require. Treating the bare label as
// sufficient made the gate permanently unsatisfiable for human
// pull requests closing such issues, because the contract they
// were measured against had never been written (#1130). Only the
// two contract labels count here -- the generic `agent` label is
// never recognised by the snapshot job or the collector, so it
// stays a pull-request-side provenance signal only.
const issueLabelSource = selectedIssue && selectedIssue.labels
? Array.isArray(selectedIssue.labels)
? selectedIssue.labels
: selectedIssue.labels.nodes || []
: [];
const issueDispatch =
carriesLabel(issueLabelSource, ['agenttask', 'mcpagent']) &&
declaresAgentContract(selectedIssue);
return login !== 'dependabot[bot]' &&
(pullProvenance || issueDispatch);
}
function intentContractErrors(issue, comments, pullCreatedAt) {
const errors = [];
Expand Down Expand Up @@ -1876,6 +1921,43 @@ jobs:
return String(value || '').toLowerCase()
.replace(/[^a-z0-9 ]/g, '').trim();
}
function carriesLabel(source, accepted) {
return (source || []).map(label =>
typeof label === 'string' ? label : label.name
).map(normaliseLabel).some(label =>
accepted.includes(label)
);
}
function declaresAgentContract(issue) {
function section(body, headings) {
const wanted = new Set(headings.map(normaliseLabel));
const lines = String(body || '').split(/\r?\n/);
const output = [];
let collecting = false;
for (const line of lines) {
const heading = line.match(/^#{2,6}\s+(.+?)\s*$/);
if (heading) {
if (collecting) {
break;
}
collecting = wanted.has(normaliseLabel(heading[1]));
continue;
}
if (collecting) {
output.push(line);
}
}
return output.join('\n').trim();
}
function declared(headings) {
const value = section(
String((issue && issue.body) || ''), headings
).replace(/^\x60|\x60$/g, '').trim();
return Boolean(value) && value !== '_No response_';
}
return declared(['agent run id', 'run id']) &&
declared(['agent login']);
}
const login = String(
pull && pull.user && pull.user.login || ''
);
Expand All @@ -1886,21 +1968,6 @@ jobs:
'openai-codex[bot]',
'chatgpt-codex-connector[bot]'
]);
const labels = [
...((pull && pull.labels) || []).map(label =>
typeof label === 'string' ? label : label.name
),
...(selectedIssue && selectedIssue.labels
? Array.isArray(selectedIssue.labels)
? selectedIssue.labels
: selectedIssue.labels.nodes || []
: []).map(label =>
typeof label === 'string' ? label : label.name
)
].map(normaliseLabel);
const agentLabel = labels.some(label =>
['agent', 'agenttask', 'mcpagent'].includes(label)
);
const agentBranch =
/^(?:agent|claude|codex|copilot|jules)[/-]/i.test(
String(pull && pull.head && pull.head.ref || '')
Expand All @@ -1909,10 +1976,33 @@ jobs:
/<!--\s*agent-lock-manifest\s*[\s\S]*?-->/i.test(
String(pull && pull.body || '')
);
return login !== 'dependabot[bot]' && (
knownAgents.has(login) || agentBranch || agentLabel ||
manifestPresent
);
// Provenance asserted by the pull request itself. Each of these
// is a claim by the producing side that this is agent work.
const pullProvenance = knownAgents.has(login) || agentBranch ||
manifestPresent ||
carriesLabel((pull && pull.labels) || [],
['agent', 'agenttask', 'mcpagent']);
// Issue-side dispatch. Label automation also applies agent task
// labels as topic tags to issues that never declared a contract,
// so the bare label is not evidence of a dispatch: it only
// counts when the issue actually declares the run id and login
// the gate goes on to require. Treating the bare label as
// sufficient made the gate permanently unsatisfiable for human
// pull requests closing such issues, because the contract they
// were measured against had never been written (#1130). Only the
// two contract labels count here -- the generic `agent` label is
// never recognised by the snapshot job or the collector, so it
// stays a pull-request-side provenance signal only.
const issueLabelSource = selectedIssue && selectedIssue.labels
? Array.isArray(selectedIssue.labels)
? selectedIssue.labels
: selectedIssue.labels.nodes || []
: [];
const issueDispatch =
carriesLabel(issueLabelSource, ['agenttask', 'mcpagent']) &&
declaresAgentContract(selectedIssue);
return login !== 'dependabot[bot]' &&
(pullProvenance || issueDispatch);
}

const prNumber = Number(process.env.INPUT_PR_NUMBER || 0);
Expand Down Expand Up @@ -2163,6 +2253,27 @@ jobs:
.replace(/^\x60|\x60$/g, '').trim();
const expectedAgentLogin = section(issueBody, ['agent login'])
.replace(/^\x60|\x60$/g, '').trim();
// A linked issue carrying an agent task label without declaring a
// run id and login was never dispatched to an agent -- the label
// is topic noise. Surface the mislabel so it is visible and
// correctable (#1130). Keyed on the missing contract itself, not
// on inapplicability, so pull requests that are inapplicable for
// other reasons (for example Dependabot's unconditional
// exclusion) never receive a notice falsely claiming a declared
// contract is missing.
const contractDeclared = [expectedRunId, expectedAgentLogin]
.every(value => value && value !== '_No response_');
if (issue && !contractDeclared &&
['agenttask', 'mcpagent'].some(label =>
issueLabels.has(label))) {
core.notice(
'mislabelled_agent_task: issue #' + issue.number +
' carries an agent task label but declares no Agent Run ID' +
' / Agent Login. The label asserts an agent dispatch' +
' contract; remove it from the issue if it was applied in' +
' error.'
);
}
if (applicable && !expectedRunId) {
collectionErrors.push('missing_agent_run_id');
} else if (applicable && String(manifest.run_id || '') !== expectedRunId) {
Expand Down
Loading
Loading