Fix centralized workflow dispatch API usage and status comment attribution#42077
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
|
✅ PR Code Quality Reviewer completed the code quality review. |
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. No ADR enforcement needed: PR #42077 does not have the 'implementation' label and has 0 new lines of code in business logic directories (threshold: 100). |
|
✅ Test Quality Sentinel completed test quality analysis. |
There was a problem hiding this comment.
Pull request overview
This PR updates centralized slash-command routing to dispatch workflows via the workflow dispatch REST endpoint (with optional return_run_details handling) and propagates dispatched run/workflow metadata into reusable status comments so the comment links to the dispatched run and shows the dispatched workflow name.
Changes:
- Switch dispatch logic to
POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches, retrying withoutreturn_run_detailswhen unsupported, and normalizing run metadata from response variants. - Enrich forwarded
aw_contextwith dispatched workflow/run metadata and prefer these fields when rebuilding reusable status comments. - Extend status comment rendering to support an explicit workflow-name override and add targeted tests for dispatch + comment updates.
Show a summary per file
| File | Description |
|---|---|
| actions/setup/js/route_slash_command.cjs | Updates dispatch to use the workflow dispatch endpoint, parses run metadata, and patches the immediate status comment with dispatched run/workflow info. |
| actions/setup/js/route_slash_command.test.cjs | Updates routing tests to mock the new dispatch endpoint behavior and assert status comment patching with dispatched run links. |
| actions/setup/js/add_workflow_run_comment.cjs | Reads dispatched run/workflow overrides from aw_context when updating reusable status comments; adds workflow-name override support to comment body builder. |
| actions/setup/js/add_workflow_run_comment.test.cjs | Adds tests verifying dispatched metadata overrides and workflow-name override precedence in comment rendering. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Low
| const runUrlFromResponse = firstNonEmptyString([responseData?.workflow_run_url, responseData?.workflow_run?.html_url, responseData?.workflow_run?.url]); | ||
| const serverUrl = context.serverUrl || process.env.GITHUB_SERVER_URL || "https://github.com"; | ||
| const runUrlFromRunId = runId ? `${serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}` : undefined; | ||
| const runUrl = runUrlFromResponse || runUrlFromRunId; |
There was a problem hiding this comment.
Review: Fix centralized workflow dispatch API usage and status comment attribution
The changes are well-structured and address the stated goals clearly. No blocking issues found.
Summary
- ✅ Dispatch API migration — switching from
github.rest.actions.createWorkflowDispatchtogithub.requestwithreturn_run_detailsis correct, and the GHES compatibility fallback is sensibly guarded. - ✅ Response normalization — handling both flat and nested
workflow_runresponse shapes is robust;Number.isFiniteguards against NaN/0 correctly. - ✅ Comment attribution —
readAwContextString,dispatchedWorkflowName || undefinedpassthrough, andbuildCommentBodyoverride are clean. - ✅ Test coverage — main paths (dispatch endpoint switch, status comment metadata, workflow name override, disabled workflow) are all exercised.
Non-blocking suggestions
Two inline comments are posted (details there):
- Missing test for the
return_run_detailsretry path — the GHES compatibility fallback has no unit test; a simple mock covering the 422 → retry → success flow would close that gap. - Multi-route last-write-wins behavior — when multiple routes have
status_comment: true, sequentialupdateStatusCommentWithDispatchcalls overwrite each other; worth documenting or acknowledging as intentional.
🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 48.7 AIC · ⌖ 8.96 AIC · ⊞ 4.8K
| throw err; | ||
| } | ||
| core.info("Workflow dispatch endpoint rejected 'return_run_details' (common on older GHES versions); retrying without it."); | ||
| response = await github.request("POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches", dispatchArgs); |
There was a problem hiding this comment.
The return_run_details compatibility fallback (retrying without that parameter) has no dedicated test coverage. Consider adding a test that mocks github.request to throw a 422 error whose message includes "return_run_details" on the first call, then succeeds on the second — verifying the dispatch still completes and dispatched.dispatched is true.
@copilot please address this.
| if (dispatched.dispatched) { | ||
| core.info(`Dispatched '${workflowID}' for '/${commandName}'`); | ||
| if (maintainsStatusComment(route) && statusCommentContext) { | ||
| await updateStatusCommentWithDispatch(statusCommentContext, identifier, workflowID, dispatched.run_url); |
There was a problem hiding this comment.
When multiple routes in the same slash command have status_comment: true, this loop will call updateStatusCommentWithDispatch once per matching route. Each call overwrites the comment with the latest dispatched workflow's name/URL, so only the last dispatched workflow ends up displayed. This is probably fine for single-route configurations (the common case), but if multi-workflow slash commands with status_comment: true are expected, the behavior should be documented or the last-write-wins semantic should be made explicit.
@copilot please address this.
🧪 Test Quality Sentinel Report✅ Test Quality Score: 95/100 — Excellent
📊 Metrics & Test Classification (6 tests analyzed)
Go: 0; JavaScript: 6 ( Test inflation check:
Verdict
Minor observation:
|
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /diagnose, /tdd, and /grill-with-docs — requesting changes primarily on test coverage gaps for the GHES fallback path.
📋 Key Themes & Highlights
Key Themes
- GHES fallback untested: The
return_run_detailsretry logic is the most novel runtime behaviour in this PR, but it has zero test coverage. A future change to the error-string check would silently break GHES compatibility. - Core new behaviour not verified in tests: Tests mock dispatch and check the response is handled, but never assert
return_run_details: truewas actually sent, making the key intent unverifiable. - Graceful-degradation path needs a test: When dispatch returns no
run_url(GHES without run-details support), the status-comment update is intentionally skipped — but there is no test asserting this. - Fragile error-string matching: The fallback hinge on a lowercase
.includes("return_run_details")match. A GHES version that rewords the error message would silently regress.
Positive Highlights
- ✅ Clean
dispatchWorkflowreturn-type upgrade frombooleanto a typed object — well-structured for future extension. - ✅
readAwContextStringhelper is a nice defensive primitive; consistent null/type guard pattern. - ✅
updateStatusCommentWithDispatchswallows errors tocore.warning— good non-fatal treatment. - ✅ Test for dispatched workflow name override in comment body is exactly the right kind of assertion.
- ✅ Two-shape response normalisation (
workflow_run_idflat vsworkflow_runnested) is clearly documented.
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 103.7 AIC · ⌖ 7.75 AIC · ⊞ 6.6K
Comments that could not be inline-anchored
actions/setup/js/route_slash_command.cjs:344
[/diagnose] No test covers the return_run_details GHES compatibility fallback — this is the highest-risk unverified path in the PR.
<details>
<summary>💡 Suggested test skeleton</summary>
it("retries dispatch without return_run_details when GHES rejects it", async () => {
let callCount = 0;
globals.github.request = vi.fn(async (route, params) => {
if (String(route).includes("/dispatches")) {
callCount++;
if (callCount === 1) {
const err = Object.assign(…
</details>
<details><summary>actions/setup/js/route_slash_command.test.cjs:197</summary>
**[/tdd]** The test asserts the API version header is sent, but doesn't verify `return_run_details: true` is included in the dispatch request — the central new behaviour of this PR.
<details>
<summary>💡 Add the assertion</summary>
```js
expect(dispatchCalls[0].return_run_details).toBe(true);Without this, the return_run_details flag could be silently dropped in a refactor and no test would catch it.
</details>
@copilot please address this.
actions/setup/js/route_slash_command.cjs:345
[/diagnose] The GHES fallback trigger relies on message.includes("return_run_details") — a brittle string-match that will silently stop working if GitHub GHES changes its error wording.
<details>
<summary>💡 Consider a more defensive approach</summary>
The current check:
const mentionsReturnRunDetails = typeof message === 'string' && message.toLowerCase().includes('return_run_details');A minor phrasing change in a GHES release (e.g. "unrecognized parameter" vs *"unknown pa…
actions/setup/js/route_slash_command.test.cjs:251
[/tdd] The test verifies the status comment is updated when run_url is present, but there is no complementary test asserting the update is skipped when dispatched.run_url is undefined — the expected degraded-mode behaviour on GHES without run-details support.
<details>
<summary>💡 What to add</summary>
Add a test where the dispatch mock returns { data: {} } (no run details) and assert that no PATCH /repos/.../issues/comments/... call is made. This guards `updateStatusCommentWi…
actions/setup/js/route_slash_command.cjs:383
[/grill-with-docs] The parameter is named eventName but it receives identifier (the output of eventIdentifier()), which can be "pull_request_comment" — a gh-aw–specific value, not a raw GitHub event name. The two concepts are different and the naming may confuse future maintainers.
<details>
<summary>💡 Suggestion</summary>
Rename the parameter to eventIdentifier to match the call-site variable and the project's own terminology, and update the JSDoc @param accordingly:
…
</details>
<details><summary>actions/setup/js/add_workflow_run_comment.cjs:360</summary>
**[/diagnose]** Double-trim: `readAwContextString` already calls `.trim()` on the returned value, so `workflowNameOverride?.trim()` here is redundant. Harmless, but creates a subtle expectation mismatch — callers passing other (non-aw_context) values still need to ensure they pass trimmed strings, yet the trim in `readAwContextString` only helps aw_context callers.
<details>
<summary>💡 Options</summary>
1. Remove the `.trim()` in `buildCommentBody` and document that callers are responsible f…
</details>
<details><summary>actions/setup/js/route_slash_command.cjs:302</summary>
**[/tdd]** `firstNonEmptyString` is a small but non-trivial helper with edge-case sensitivity (it treats whitespace-only strings as empty). It has no direct unit tests — it's exercised only through the end-to-end dispatch path.
<details>
<summary>💡 Suggested unit tests</summary>
```js
describe('firstNonEmptyString', () => {
it('returns the first non-empty string', () => {
expect(firstNonEmptyString([null, undefined, '', ' ', 'valid'])).toBe('valid');
});
it('returns empty string w…
</details>There was a problem hiding this comment.
REQUEST_CHANGES — The dispatch and metadata-propagation logic has correctness gaps that should be addressed before merging.
### Blocking issues (5)
High
- Fan-out loop overwrites status comment (
route_slash_command.cjs:760):updateStatusCommentWithDispatchis called inside the route loop; when multiple routes both maintain a status comment, each dispatch stomps the previous one and only the last dispatch's metadata survives.
Medium
- Fragile
return_run_detailsfallback (route_slash_command.cjs:344): The GHES compatibility retry only fires when the error message literally contains"return_run_details". Alternative error phrasings from different GHES versions convert the fallback into a hard failure. Widening the retry condition to any 400/422 is safer. - Silent no-op on old GHES (
route_slash_command.cjs:387): When the server returns no run URL (GHES fallback path),updateStatusCommentWithDispatchreturns early without anycore.info/core.warning. The status comment is silently left unpatched with no diagnostic signal. - Workflow-ID marker/name desync (
add_workflow_run_comment.cjs:361):workflowNameis overridden to the dispatched workflow's name for user-visible text, butgenerateWorkflowIdMarkerstill usesprocess.env.GITHUB_WORKFLOW(the router). The dispatched workflow's ownadd_workflow_run_commentstep will fail to find the existing comment by marker and create a duplicate. - No test for the
return_run_detailsfallback retry (route_slash_command.test.cjs:251): The inner try/catch and retry-without-parameter path is entirely untested. A regression would go undetected until GHES production failures.
🔎 Code quality review by PR Code Quality Reviewer · 108.7 AIC · ⌖ 7.66 AIC · ⊞ 5.2K
| if (dispatched.dispatched) { | ||
| core.info(`Dispatched '${workflowID}' for '/${commandName}'`); | ||
| if (maintainsStatusComment(route) && statusCommentContext) { | ||
| await updateStatusCommentWithDispatch(statusCommentContext, identifier, workflowID, dispatched.run_url); |
There was a problem hiding this comment.
Status comment stomped on each loop iteration in fan-out scenarios: updateStatusCommentWithDispatch is called inside the routes loop, so when multiple routes both maintainsStatusComment, every successful dispatch overwrites the same comment — the last writer wins and all earlier dispatch metadata is silently lost.
💡 Impact and suggested fix
In the existing test creates an immediate status comment once and forwards it in aw_context, two routes match /archie (both status_comment: true). Both dispatches succeed, so updateStatusCommentWithDispatch is called twice with the same status_comment_id. The test only asserts statusUpdateCalls[0], so the second overwrite goes unobserved. In production, the comment would show only the last dispatched workflow's run link.
Options:
- Collect
{ workflowId, runUrl }per-route and perform a single comment update after the loop. - Skip the router-side update for multi-route fans and let each dispatched workflow's own
add_workflow_run_commentstep handle attribution. - At minimum, tighten the assertion: verify the final comment state after both dispatches, not just
statusUpdateCalls[0].
| const status = err && typeof err === "object" ? err.status : undefined; | ||
| const message = typeof err?.response?.data?.message === "string" ? err.response.data.message : String(dispatchError); | ||
| const isValidationStatus = status === 400 || status === 422; | ||
| const mentionsReturnRunDetails = typeof message === "string" && message.toLowerCase().includes("return_run_details"); |
There was a problem hiding this comment.
return_run_details detection relies on a fragile substring match that will silently hard-fail dispatch on GHES with different error phrasing: the inner catch only retries when the error is both a 400/422 status AND its message contains the literal string "return_run_details". If a GHES version rejects the parameter with a different message (e.g., "unexpected_input", "invalid field", etc.), mentionsReturnRunDetails is false, the inner catch re-throws, and the outer catch turns it into a hard Failed to dispatch workflow failure instead of the intended silent retry.
💡 Suggested fix
Retry on all 400/422 errors — the worst case from a false positive is an unnecessary second request. Since return_run_details is the only added field and valid GHES endpoints won't reject a vanilla dispatch, retrying without it on any 4xx is safe:
const isValidationStatus = status === 400 || status === 422;
if (!isValidationStatus) {
throw err;
}
core.info("Workflow dispatch endpoint returned 4xx; retrying without return_run_details (GHES compatibility fallback).");
response = await github.request(...);This is strictly more robust and no more dangerous than the current implementation.
| if (!statusCommentContext?.status_comment_id) { | ||
| return; | ||
| } | ||
| if (!runUrl) { |
There was a problem hiding this comment.
Status comment is silently left stale when the server returns no run URL (old GHES): when return_run_details is rejected and the retry response contains no run metadata, dispatched.run_url is undefined, and this guard returns early with no core.warning. The status comment created by addImmediateStatusComment() is never updated with the dispatched workflow name — the PR's primary goal — and there is no observable signal that the enrichment was skipped.
💡 Suggested improvement
Add a log so that operators can diagnose missing enrichment in GHES environments:
if (!runUrl) {
core.info(`Skipping status comment enrichment for '${workflowId}': dispatch response contained no run URL (older GHES may not support return_run_details).`);
return;
}If workflow-name attribution is considered important enough to justify the effort, you could also patch the comment with just the dispatched workflow name even when runUrl is unavailable, rather than skipping the update entirely.
| function buildCommentBody(eventName, runUrl, workflowNameOverride) { | ||
| // Whitespace-only overrides are treated as absent and fall back to env defaults. | ||
| const normalizedWorkflowNameOverride = workflowNameOverride?.trim(); | ||
| const workflowName = normalizedWorkflowNameOverride || process.env.GH_AW_WORKFLOW_NAME || process.env.GITHUB_WORKFLOW || "Workflow"; |
There was a problem hiding this comment.
Visible workflow name and hidden workflow-ID marker are now out of sync: workflowName (user-visible) correctly uses workflowNameOverride, but the generateWorkflowIdMarker call a few lines below still uses process.env.GITHUB_WORKFLOW (the router's name). When the dispatched workflow archie.lock.yml later runs its own add_workflow_run_comment step, it searches for an existing status comment tagged with marker archie — but the comment was tagged with the router's marker (e.g., agentic-commands). The match fails, a second status comment is created, and the original router-enriched comment is orphaned.
💡 Suggested fix
Pass the workflow name override through to the marker as well:
const workflowId = workflowNameOverride || process.env.GH_AW_WORKFLOW_NAME || process.env.GITHUB_WORKFLOW || "";
if (workflowId) {
body += `\n\n${generateWorkflowIdMarker(workflowId)}`;
}Alternatively, keep the router marker intentionally and document that the reuse contract relies on status_comment_id rather than marker matching — but the current code makes this implicit and fragile.
| expect(globals.github.request.mock.calls.filter(([route]) => /\/issues\/77\/comments$/.test(String(route)))).toHaveLength(1); | ||
| const statusUpdateCalls = globals.github.request.mock.calls.filter(([route]) => String(route).startsWith("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}")); | ||
| expect(statusUpdateCalls.length).toBeGreaterThan(0); | ||
| expect(statusUpdateCalls[0][1].body).toContain("[archie](https://github.com/github/gh-aw/actions/runs/444)"); |
There was a problem hiding this comment.
No test covers the return_run_details fallback retry path: the new GHES compatibility code (inner try/catch, retry without the parameter) has zero coverage. This is the critical path for older GHES compatibility and it's entirely untested.
💡 Missing test case
Add a test that:
- Mocks
github.requestto throw{ status: 422, response: { data: { message: "Unsupported parameter return_run_details" } } }on the first dispatch call. - Returns
{ data: {} }on the second dispatch call. - Asserts that
dispatchCallshas exactly 2 entries, the second of which has noreturn_run_detailsfield. - Asserts that the dispatch is still reported as successful (no
setFailed). - Asserts that
updateStatusCommentWithDispatchis NOT called (since the retry response has no run URL).
Without this test, a refactor that breaks the fallback will go undetected until a production GHES deployment fails.
|
@copilot run pr-finisher skill |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
🎉 This pull request is included in a new release. Release: |
Centralized slash-command routing was dispatching workflows via an API path that emits deprecation warnings and did not reliably propagate dispatched run metadata into status comments. This change updates dispatch behavior so status comments link to the dispatched run and display the dispatched workflow name (instead of
Agentic Commands).Dispatch API + response handling
return_run_detailswhen supported, with compatibility fallback when not.workflow_run_id/workflow_run.*).Status comment metadata propagation
aw_contextwith:dispatched_workflow_namedispatched_run_urlWorkflow name correction in comment body
Targeted test coverage updates