Skip to content
Open
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
5 changes: 5 additions & 0 deletions .github/aw/actions-lock.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"entries": {
"actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803": {
"repo": "actions/checkout",
"version": "d23441a48e516b6c34aea4fa41551a30e30af803",
"sha": "d23441a48e516b6c34aea4fa41551a30e30af803"
},
"actions/checkout@v7.0.1": {
"repo": "actions/checkout",
"version": "v7.0.1",
Expand Down
156 changes: 156 additions & 0 deletions .github/workflows/issue-investigation-handoff.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Consume completed triage without changing its jobs, outputs, or failure handling.
name: Issue Investigation Handoff
on:
workflow_run:
workflows: [Agentic Triage]
types: [completed]
permissions: {}
concurrency:
group: issue-investigation-handoff-${{ github.event.workflow_run.id }}
cancel-in-progress: false
jobs:
handoff:
if: >-
vars.GH_AW_ENABLE_ISSUE_INVESTIGATION == 'true' &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_repository.full_name == github.repository &&
github.event.workflow_run.head_branch == github.event.repository.default_branch
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
issues: read
actions: write
steps:
- name: Checkout trusted handoff helper
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
ref: ${{ github.workflow_sha }}
persist-credentials: false
sparse-checkout: .github/workflows/scripts
path: triage-handoff
- name: Select successful triage and current artifacts
id: triage
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const path = require('node:path');
const helper = require(path.join(process.env.GITHUB_WORKSPACE, 'triage-handoff',
'.github', 'workflows', 'scripts', 'completed_triage_handoff.cjs'));
const triage = await helper.selectCompletedTriage({ github, context });
if (triage.reason) {
core.notice(`Investigation handoff skipped: ${triage.reason}`);
return;
}
for (const [name, value] of Object.entries(triage)) core.setOutput(name, String(value));
- name: Setup native safe-output processor
if: steps.triage.outputs.runId != ''
uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.8
with:
destination: ${{ runner.temp }}/gh-aw/actions
- name: Download triage detection verdict
if: steps.triage.outputs.runId != ''
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
artifact-ids: ${{ steps.triage.outputs.detectionId }}
run-id: ${{ steps.triage.outputs.runId }}
github-token: ${{ github.token }}
merge-multiple: true
path: ${{ runner.temp }}/triage-detection
- name: Download applied triage receipts
if: steps.triage.outputs.runId != ''
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
artifact-ids: ${{ steps.triage.outputs.receiptId }}
run-id: ${{ steps.triage.outputs.runId }}
github-token: ${{ github.token }}
merge-multiple: true
path: ${{ runner.temp }}/triage-receipts
- name: Download triage routing requests
if: steps.triage.outputs.runId != ''
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
artifact-ids: ${{ steps.triage.outputs.requestId }}
run-id: ${{ steps.triage.outputs.runId }}
github-token: ${{ github.token }}
merge-multiple: true
path: ${{ runner.temp }}/triage-requests
- name: Validate completed triage and dispatch investigation
id: handoff
if: steps.triage.outputs.runId != ''
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
TRIAGE_EVENT: ${{ steps.triage.outputs.event }}
TRIAGE_ISSUE_NUMBER: ${{ steps.triage.outputs.issueNumber }}
TRIAGE_RUN_ID: ${{ steps.triage.outputs.runId }}
TRIAGE_RUN_ATTEMPT: ${{ steps.triage.outputs.attempt }}
TRIAGE_OWNER_NOTIFICATION: ${{ steps.triage.outputs.ownerNotification }}
GH_AW_DETECTION_CONCLUSION: success
GH_AW_WORKFLOW_ID: issue-triage
GH_AW_WORKFLOW_NAME: Agentic Triage
GH_AW_CALLER_WORKFLOW_ID: ${{ github.repository }}/issue-triage
with:
script: |
const fs = require('node:fs');
const path = require('node:path');
const helperDir = path.join(process.env.GITHUB_WORKSPACE, 'triage-handoff',
'.github', 'workflows', 'scripts');
const { assertSafeDetection } = require(path.join(helperDir, 'completed_triage_handoff.cjs'));
assertSafeDetection(JSON.parse(fs.readFileSync(path.join(
process.env.RUNNER_TEMP, 'triage-detection', 'detection_result.json'), 'utf8')));
const { positiveInteger } = require(path.join(helperDir, 'issue_workflow_support.cjs'));
const number = positiveInteger(process.env.TRIAGE_ISSUE_NUMBER, 'Triage issue number');
const { data: parent } = await github.rest.actions.getWorkflowRun({
...context.repo, run_id: positiveInteger(process.env.TRIAGE_RUN_ID, 'Triage run ID'),
});
if (parent.status !== 'completed' || parent.conclusion !== 'success' ||
parent.run_attempt !== Number(process.env.TRIAGE_RUN_ATTEMPT)) {
core.notice('Investigation handoff skipped: parent triage was rerun during validation');
return;
}
const appliedItems = fs.readFileSync(path.join(
process.env.RUNNER_TEMP, 'triage-receipts', 'safe-output-items.jsonl'), 'utf8')
.split(/\r?\n/).filter(line => line.trim()).map(line => JSON.parse(line));
if (appliedItems.length === 0) {
core.notice('Investigation handoff skipped: no applied triage mutations (including staged runs)');
return;
}
const agentOutput = JSON.parse(fs.readFileSync(path.join(
process.env.RUNNER_TEMP, 'triage-requests', 'agent_output.json'), 'utf8'));
require(path.join(helperDir, 'issue_workflow_support.cjs')).validateAgentOutput(agentOutput);
const triageContext = {
...context, eventName: process.env.TRIAGE_EVENT,
payload: { ...context.payload,
...(process.env.TRIAGE_EVENT === 'issues' ? { issue: { number } } : {}) },
};
Comment on lines +121 to +125
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
require(path.join(actionsDir, 'setup_globals.cjs'))
.setupGlobals(core, github, triageContext, exec, io, getOctokit);
const { extractAssignees, resolveIssueNumber } = require(path.join(actionsDir, 'safe_output_helpers.cjs'));
const { processItems } = require(path.join(actionsDir, 'safe_output_processor.cjs'));
const { prepareHandoff } = require(path.join(helperDir, 'issue_investigation_handoff.cjs'));
const handoff = await prepareHandoff({
github, context: triageContext, issueNumber: number, appliedItems, agentOutput,
ownerNotification: process.env.TRIAGE_OWNER_NOTIFICATION,
normalizeAssignment: item => ({ ...resolveIssueNumber(item),
assignees: processItems(extractAssignees(item), [], 1, []) }),
});
if (!handoff.output) {
core.notice(`Investigation handoff skipped: ${handoff.reason}`);
return;
}
const file = path.join(process.env.RUNNER_TEMP, 'investigation-handoff.json');
fs.writeFileSync(file, JSON.stringify(handoff.output));
core.setOutput('dispatch_requested', 'true');
process.env.GH_AW_AGENT_OUTPUT = file;
process.env.GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG = JSON.stringify(handoff.config);
const { MANIFEST_FILE_PATH } = require(path.join(actionsDir, 'constants.cjs'));
fs.mkdirSync(path.dirname(MANIFEST_FILE_PATH), { recursive: true });
await require(path.join(actionsDir, 'process_safe_outputs.cjs')).main();
- name: Confirm investigation dispatch succeeded
if: >-
steps.handoff.outputs.dispatch_requested == 'true' &&
(steps.handoff.outputs.status != 'success' || steps.handoff.outputs.items_applied != '1')
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: core.setFailed('The native safe-output processor did not apply the investigation dispatch');
Loading
Loading