-
Notifications
You must be signed in to change notification settings - Fork 528
Support temporary IDs for dispatched workflow runs #53572
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ const path = require("node:path"); | |
| const { isStagedMode } = require("./safe_output_helpers.cjs"); | ||
| const { logStagedPreviewInfo } = require("./staged_preview.cjs"); | ||
| const { checkFileProtectionPostApply } = require("./manifest_file_helpers.cjs"); | ||
| const { loadTemporaryIdMapFromResolved, resolveIssueNumber } = require("./temporary_id.cjs"); | ||
|
|
||
| /** @type {string} Safe output type handled by this module */ | ||
| const HANDLER_TYPE = "approve_workflow_run"; | ||
|
|
@@ -129,10 +130,17 @@ async function main(config = {}) { | |
|
|
||
| const githubClient = isStaged ? null : await createAuthenticatedGitHubClient(config); | ||
|
|
||
| return async function handleApproveWorkflowRun(message) { | ||
| const runId = parsePositiveInt(message.run_id); | ||
| return async function handleApproveWorkflowRun(message, resolvedTemporaryIds = {}) { | ||
| const resolvedRunId = resolveIssueNumber(message.run_id, loadTemporaryIdMapFromResolved(resolvedTemporaryIds)); | ||
| const runId = resolvedRunId.wasTemporaryId ? (resolvedRunId.resolved?.number ?? undefined) : parsePositiveInt(message.run_id); | ||
|
Comment on lines
+134
to
+135
|
||
| if (!runId) { | ||
| const error = "run_id must be a positive integer"; | ||
| const error = (resolvedRunId.wasTemporaryId ? resolvedRunId.errorMessage : null) || "run_id must be a positive integer or resolved temporary ID"; | ||
| core.warning(error); | ||
| return { success: false, error }; | ||
| } | ||
| const resolvedRepo = resolvedRunId.resolved?.repo; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] 💡 Suggested fix + regression testGuard before comparing: const resolvedRepo = resolvedRunId.resolved?.repo;
if (resolvedRunId.wasTemporaryId) {
if (!resolvedRepo) {
const error = `Temporary workflow run ID '${message.run_id}' does not contain a repository reference`;
core.warning(error);
return { success: false, error };
}
if (resolvedRepo !== `${context.repo.owner}/${context.repo.repo}`) {
const error = `Temporary workflow run ID '${message.run_id}' belongs to ${resolvedRepo}, not ${context.repo.owner}/${context.repo.repo}`;
core.warning(error);
return { success: false, error };
}
}And add a test: it("rejects a temporary ID with missing repo field", async () => {
const handler = await main(externalTokenConfig);
const result = await handler({ run_id: "aw_run123" }, { aw_run123: { number: 123 } });
expect(result.success).toBe(false);
expect(result.error).toContain("does not contain a repository reference");
});@copilot please address this. |
||
| if (resolvedRunId.wasTemporaryId && resolvedRepo !== `${context.repo.owner}/${context.repo.repo}`) { | ||
| const error = `Temporary workflow run ID '${message.run_id}' belongs to ${resolvedRepo}, not ${context.repo.owner}/${context.repo.repo}`; | ||
| core.warning(error); | ||
| return { success: false, error }; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -325,6 +325,9 @@ async function main(config = {}) { | |
| core.info(`✓ Successfully dispatched workflow: ${workflowFile} (run ID: ${runId})`); | ||
| } else { | ||
| core.info(`✓ Successfully dispatched workflow: ${workflowFile}`); | ||
| if (typeof message.temporary_id === "string") { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] Missing test for 💡 Suggested testit("should not include temporaryId when API returns no workflow_run_id", async () => {
github.rest.actions.createWorkflowDispatch.mockResolvedValueOnce({ data: {} }); // no workflow_run_id
const handler = await main({ workflows: ["test-workflow"], workflow_files: { "test-workflow": ".lock.yml" } });
const result = await handler({ type: "dispatch_workflow", workflow_name: "test-workflow", temporary_id: "aw_run123", inputs: {} }, {});
expect(result.success).toBe(true);
expect(result.temporaryId).toBeUndefined();
expect(core.warning).toHaveBeenCalledWith(expect.stringContaining("aw_run123"));
});Without this test the warning path is exercised only manually. @copilot please address this. |
||
| core.warning(`Unable to register temporary ID '${message.temporary_id}' because the GitHub API did not return a workflow run ID.`); | ||
| } | ||
| } | ||
|
|
||
| // Record the time of this dispatch for rate limiting | ||
|
|
@@ -335,6 +338,7 @@ async function main(config = {}) { | |
| workflow_name: workflowName, | ||
| inputs: inputs, | ||
| run_id: runId, | ||
| ...(typeof message.temporary_id === "string" && runId ? { temporaryId: message.temporary_id, repo: resolvedRepoSlug, number: runId } : {}), | ||
| }; | ||
| } catch (error) { | ||
| const errorMessage = getErrorMessage(error); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -251,6 +251,21 @@ describe("safe_outputs_tools_loader", () => { | |
| }); | ||
| }); | ||
|
|
||
| it("should pass temporary_id separately from dispatch workflow inputs", () => { | ||
| const tools = [{ name: "ci_workflow", description: "CI workflow", _workflow_name: "ci" }]; | ||
| const mockHandlerFunction = vi.fn(); | ||
| const handlers = { defaultHandler: vi.fn(() => mockHandlerFunction) }; | ||
|
|
||
| const result = attachHandlers(tools, handlers); | ||
| result[0].handler({ environment: "staging", temporary_id: "aw_run123" }); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] The 💡 Suggested assertion to addexpect(mockHandlerFunction).toHaveBeenCalledWith(
expect.objectContaining({
temporary_id: "aw_run123",
inputs: expect.not.objectContaining({ temporary_id: expect.anything() }),
})
);Without this, a regression where @copilot please address this. |
||
|
|
||
| expect(mockHandlerFunction).toHaveBeenCalledWith({ | ||
| workflow_name: "ci", | ||
| temporary_id: "aw_run123", | ||
| inputs: { environment: "staging" }, | ||
| }); | ||
| }); | ||
|
|
||
| it("should pass ref as top-level field for dispatch_workflow handler", () => { | ||
| const tools = [{ name: "ci_workflow", description: "CI workflow", _workflow_name: "ci" }]; | ||
| const mockHandlerFunction = vi.fn(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new
approve_workflow_runpath trustsresolvedTemporaryIds[...].numberwithout re-validating it as a positive integer, so a malformed or fractional mapping can now flow straight intogetWorkflowRun/approval instead of being rejected like directrun_idinput is.💡 Why this needs fixing
resolveIssueNumber()convertsnumberwithNumber(...), but this call site never re-appliesparsePositiveInt()when the source was a temporary ID:That means values like
1.5,0,-1, orNaNcoming from the resolved map can bypass the existing integer validation path and get sent to the Actions API. At best this becomes a flaky runtime failure; at worst it weakens the contract thatapprove_workflow_runonly accepts positive integer run IDs.Please normalize the resolved temporary-ID value through the same positive-integer validator before using it, and add a test that rejects a temporary ID mapped to a non-integer/non-positive
number.