Repository navigation
Add attributed issue creation for custom safe-output jobs - #65612
Conversation
Share built-in issue attribution, expose workflow metadata to custom jobs, and migrate AW issue clustering to preserve generated-by provenance. Document the supported API and custom attribution requirements. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Rename the issue-creation entry point and its tests, update consumers and documentation, and keep OpenTelemetry helpers separate. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Require whole-word clipping with literal ... included in the field length budget, document the convention, and cover ellipsis preservation in both publisher validation paths. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use main's JavaScript collector, artifact-backed plan publication, assignment reconciliation and completed-source cleanup. Discard branch-only clipping changes and reapply only the create-issue.cjs footer integration with regression coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Remove create-issue.cjs by consolidating issue creation in create_issue.cjs. Re-export setupGlobals, createIssue and otlp.logSpan from index.cjs, update custom-job consumers and documentation, and verify the exports preserve their original implementations. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
✅ PR Code Quality Reviewer completed the code quality review.
|
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅
|
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. See the comment below for the result and any generated ADR draft. Warning Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding. What happenedThe threat detection engine failed to produce results. Review the workflow run logs for details.
|
|
✅ Test Quality Sentinel completed test quality analysis. Test Quality Sentinel skipped because pre-fetch PR data was unavailable: unable to fetch test file diff
|
|
✅ Ponytail Reviewer completed successfully! Lean already. Ship.
|
Comment MemoryNote This comment is managed by comment memory.It stores persistent context for this thread in the code block at the top of this comment.
|
There was a problem hiding this comment.
Verdict
No blocking issues in the changed lines I reviewed.
Why I’m not requesting changes
I checked the new public actions/setup/js/index.cjs surface, the shared issue-body formatter, the safe-job environment propagation, and the aw-issue-clustering migration to the attributed issue API. The helper reuses the existing footer/provenance path instead of inventing a parallel format, keeps cross-repo issue attribution pointed at the originating workflow run, and the compiler change wires the required metadata into custom safe jobs.
🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt54 · 63.7 AIC · ⌖ 7.29 AIC · ⊞ 19.4K
Comment /review to run again
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The implementation preserves built-in behavior, validates the public API, and consistently regenerates affected workflows.
Review effort: Balanced
Findings: None
What changed in this PR
Adds a public attributed issue-creation API for custom safe-output jobs and adopts it in issue clustering.
Changes:
- Exposes
setupGlobals,createIssue, andlogSpan. - Shares issue attribution formatting between built-in and custom handlers.
- Propagates metadata to custom jobs and documents the API.
| File | Description |
|---|---|
pkg/workflow/safe_jobs.go |
Adds attribution environment variables to custom jobs. |
pkg/workflow/safe_jobs_test.go |
Tests metadata propagation and overrides. |
docs/src/content/docs/reference/safe-outputs.md |
Documents custom-output attribution. |
docs/src/content/docs/reference/open-telemetry.mdx |
Documents the logSpan re-export. |
docs/src/content/docs/reference/custom-safe-outputs.md |
Adds the issue API reference and example. |
actions/setup/js/issue_body.cjs |
Centralizes issue-body attribution formatting. |
actions/setup/js/index.test.cjs |
Tests the public API and attribution behavior. |
actions/setup/js/index.cjs |
Defines the public JavaScript exports. |
actions/setup/js/create_issue.cjs |
Implements attributed custom issue creation. |
.github/workflows/smoke-copilot.lock.yml |
Regenerates custom-job metadata. |
.github/workflows/smoke-copilot-arm.lock.yml |
Regenerates custom-job metadata. |
.github/workflows/smoke-copilot-aoai-entra.lock.yml |
Regenerates custom-job metadata. |
.github/workflows/smoke-copilot-aoai-apikey.lock.yml |
Regenerates custom-job metadata. |
.github/workflows/mcp-inspector.lock.yml |
Regenerates custom-job metadata. |
.github/workflows/daily-choice-test.lock.yml |
Regenerates custom-job metadata. |
.github/workflows/aw-issue-clustering.md |
Uses attributed issue creation. |
.github/workflows/aw-issue-clustering.lock.yml |
Regenerates the clustering workflow. |
.github/scripts/test_aw_issue_clustering_publish.cjs |
Tests attributed clustering output. |
.github/scripts/aw_issue_clustering_publish.cjs |
Injects the attributed creation API. |
.github/aw/shared-safe-jobs.md |
Adds attribution guidance. |
.github/aw/safe-outputs-runtime.md |
Documents runtime API usage. |
.changeset/minor-custom-safe-output-issue-api.md |
Records the minor public API addition. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Impeccable review — mixed_unclear (compiler env-var wiring + JS runtime API)
No blocking issues found. Verified:
buildJobLevelSafeOutputEnvVarswiring insafe_jobs.gocorrectly sets job-levelGH_AW_*attribution env vars for custom safe-jobs;TestBuildSafeJobsAttributionMetadatapasses (go test ./pkg/workflow/... -run TestBuildSafeJobs).createIssue/formatIssueBodyextraction increate_issue.cjs/issue_body.cjspreserves existing footer/marker generation exactly, including the trustedaw-essential-metamarker embedded by the clustering script before attribution is appended.index.cjsis a thin re-export (verified by its own test asserting identity, not wrapping).actions/setup/js/index.test.cjs(14/14) and.github/scripts/test_aw_issue_clustering_publish.cjs(27/27) pass locally.- Doc additions (
custom-safe-outputs.md,safe-outputs.md,open-telemetry.mdx) are consistent with the implemented API surface.
No high-signal, changed-line issues to flag; approving.
🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · copilot · sonnet50 · 115.6 AIC · ⌖ 13 AIC · ⊞ 8.1K
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /codebase-design and /tdd (with a /grill-with-docs pass on the new API docs) — focusing on the new createIssue public API surface, the strongest-impact addition in this PR.
📋 Key Themes & Highlights
Key Themes
- Interface parity gap:
createIssue()reusesformatIssueBodyfrom the built-in handler (good — single source of truth for attribution), but does not reuse the label/assignee sanitization (sanitizeLabelContent) or count limits (MAX_LABELS/MAX_ASSIGNEES, viatryEnforceArrayLimit) that the built-inmain()enforces a few hundred lines later in the same file. This is a real inconsistency risk now that both paths share a module — a future refactor could easily assume parity that doesn't exist. - Test coverage: the 166-line
index.test.cjssuite is thorough on attribution, staging, body-length limits, and validation errors, but doesn't pin down behavior for labels/assignees, leaving the gap above unguarded by regression tests. - Docs precision: the new "Issue Creation API" reference section is clear and well-organized, but the parameter contract table doesn't flag the sanitization/limit gap, which could mislead an implementer into assuming full parity with the built-in handler.
Positive Highlights
- ✅ Clean extraction of
issue_body.cjs—formatIssueBodyis now shared verbatim betweencreateIssueandmain(), exactly the kind of deepening that avoids footer/attribution drift between code paths (validated by the"uses exactly the built-in issue body formatting"test). - ✅
index.cjsis a minimal, honest re-export with no wrapping — the accompanying test explicitly asserts referential identity (toBe), which is a nice contract-pinning touch. - ✅ Staged-mode, cross-repo attribution, and the 65,536-char body-limit-after-attribution behaviors are all well tested with clear, specification-style test names.
The three inline comments above cover this in detail; none are blocking for merge, but the label/assignee parity gap is worth addressing before the API sees wider custom-job adoption.
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet50 · 226.4 AIC · ⌖ 13.7 AIC · ⊞ 10K
Comment /matt to run again
| if (body.length > MAX_GITHUB_BODY_LENGTH) { | ||
| throw new Error(`${ERR_VALIDATION}: Issue body exceeds GitHub's maximum length of ${MAX_GITHUB_BODY_LENGTH} characters including attribution`); | ||
| } | ||
| const request = { ...parameters, owner, repo, body }; |
There was a problem hiding this comment.
[/codebase-design] createIssue() bypasses the label/assignee limits and sanitization (sanitizeLabelContent, MAX_LABELS, MAX_ASSIGNEES) that the built-in main() handler enforces just a few hundred lines below. Custom jobs can pass unlimited or unsanitized labels/assignees straight to github.rest.issues.create, creating an inconsistent safety boundary between the two issue-creation paths that share this module.
💡 Why this matters & suggested fix
The docstring says "title and body must be trusted or sanitized by the custom job," which covers content, but labels/assignees are structural API fields, not agent content — the same abuse vectors (label-count DoS, mention-injection via unsanitized label names) that sanitizeLabelContent/tryEnforceArrayLimit guard against in main() apply equally here. Consider extracting those two checks into a small shared helper (e.g. in limit_enforcement_helpers.cjs) that both createIssue and main() call, so the two code paths can't silently drift again.
// before spreading parameters into `request`
if (Array.isArray(parameters.labels)) {
const limited = tryEnforceArrayLimit(parameters.labels.map(sanitizeLabelContent), MAX_LABELS, "labels");
if (!limited.success) throw new Error(`${ERR_VALIDATION}: ${limited.error}`);
}@copilot please address this.
There was a problem hiding this comment.
Added label sanitization and shared label/assignee limit checks in create_issue.cjs; the built-in handler now uses the same limit check. Committed as 3022404.
| github.rest.issues.create.mockRejectedValue(error); | ||
| await expect(createIssue({ title: "Finding", body: "Details" })).rejects.toThrow("Forbidden"); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
[/tdd] No test asserts createIssue's behavior with labels/assignees exceeding the built-in limits, or containing content needing sanitization (e.g. @mention in a label). Given createIssue is a new public API surface, its contract around these fields should be pinned down by a test — either confirming it enforces the same limits as main(), or explicitly documenting/asserting the opposite.
💡 Suggested test
it("rejects or sanitizes label counts exceeding MAX_LABELS", async () => {
const labels = Array.from({ length: 21 }, (_, i) => `label-${i}`); // assuming MAX_LABELS=20
await expect(createIssue({ title: "Finding", body: "Details", labels })).rejects.toThrow(/label/i);
});Without this, the gap flagged on create_issue.cjs line 69 could regress silently even after a fix.
@copilot please address this.
There was a problem hiding this comment.
Added regression tests for mention sanitization and for rejecting over-limit labels or assignees before any API write. Committed as 3022404.
| if (items.length !== 1 || typeof items[0].body !== "string") { | ||
| throw new Error("Exactly one finding with a string body is required"); | ||
| } | ||
| const result = await createIssue({ |
There was a problem hiding this comment.
[/grill-with-docs] The API contract table says createIssue(parameters) "Accepts Octokit issue-creation parameters" but doesn't mention that, unlike the built-in create-issue handler, it skips label/assignee sanitization and count limits. A reader following only this doc could reasonably assume parity with the built-in handler's safety guarantees.
💡 Suggested doc addition
Add a line to the contract row or the paragraph below the table, e.g.:
Unlike the built-in
create-issuehandler,createIssuedoes not sanitize or caplabels/assignees; the custom job is responsible for those checks if it accepts agent-controlled values for these fields.
This keeps the "posting helper, not the policy handler" framing already present, but makes the specific gap explicit rather than implied.
@copilot please address this.
There was a problem hiding this comment.
Updated the createIssue API contract to document sanitized labels and the shared label/assignee limits. Committed as 3022404.
|
@copilot run pr-finisher skill |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Ran the PR finisher. The review findings are fixed in |
|
🎉 This pull request is included in a new release. Release: |
Custom safe-output jobs create GitHub issues directly, bypassing the generated-by footer and provenance annotations used to identify workflow outputs. AW issue clustering exposed this gap: its issues lacked the standard attribution.
Changes
actions/setup/js/index.cjsentry point, re-exportingsetupGlobals,createIssue, and the existing OpenTelemetrylogSpanhelper without wrappers.createIssuein the existingcreate_issue.cjshandler module; there is no separatecreate-issue.cjs. It shares body formatting with the built-in handler, preserves trusted custom metadata, supports staged previews, and retains original workflow-run attribution for cross-repository issues.otlp.cjsimports remain supported.mainand preserve its clustering implementation, including JavaScript collection, artifact-backed plans, mid-run assignment reconciliation, and completed-source cleanup. Clustering changes relative to main are limited to using the attributed issue API and its regression coverage; earlier branch-only clipping changes are removed.The issue helper posts trusted or caller-sanitized content. Custom jobs remain responsible for output limits and repository policies. OpenTelemetry behavior remains implemented in
otlp.cjsand is unchanged by the re-export.Validation
Passed:
make build,make fmt,make fmt-cjs, andmake lint-cjs.make recompile: all 320 workflows compiled successfully.make agent-report-progress: formatting, lint, impacted tests, and full workflow drift validation passed.The previous
concurrency.queueschema blocker is resolved by the merged main changes. No workflows were triggered.