Skip to content

Add attributed issue creation for custom safe-output jobs - #65612

Merged
pelikhan merged 6 commits into
mainfrom
pelikhan-custom-safe-output-api
Oct 4, 2026
Merged

pelikhan merged 6 commits into
mainfrom
pelikhan-custom-safe-output-api

Conversation

@pelikhan

@pelikhan pelikhan commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

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

  • Add the supported public actions/setup/js/index.cjs entry point, re-exporting setupGlobals, createIssue, and the existing OpenTelemetry logSpan helper without wrappers.
  • Implement createIssue in the existing create_issue.cjs handler module; there is no separate create-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.
  • Pass workflow, engine, tracker, message, and detection metadata to custom jobs, regenerate affected lock files, and document attribution requirements and API usage. Direct otlp.cjs imports remain supported.
  • Merge the latest main and 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.cjs and is unchanged by the re-export.

Validation

Passed:

  • make build, make fmt, make fmt-cjs, and make lint-cjs.
  • make recompile: all 320 workflows compiled successfully.
  • make agent-report-progress: formatting, lint, impacted tests, and full workflow drift validation passed.
  • JavaScript type checking and 251 targeted public API, issue handler, and OTLP tests.
  • 43 JavaScript clustering collector/publisher tests, including upstream behavior and attributed issue creation.

The previous concurrency.queue schema blocker is resolved by the merged main changes. No workflows were triggered.

pelikhan and others added 5 commits October 4, 2026 07:44
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>
@pelikhan
pelikhan marked this pull request as ready for review October 4, 2026 16:04
Copilot AI balanced review requested due to automatic review settings October 4, 2026 16:04
@github-actions

github-actions Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

✅ PR Code Quality Reviewer completed the code quality review.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

github-actions Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

github-actions Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

✅ 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 happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

✅ Test Quality Sentinel completed test quality analysis.

Test Quality Sentinel skipped because pre-fetch PR data was unavailable: unable to fetch test file diff

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

✅ Ponytail Reviewer completed successfully!

Lean already. Ship.

Generated by Ponytail Reviewer for #65612

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Comment Memory

reviewed_at: 2026-10-04T16:07:18Z
review_event: COMMENT
top_themes:
  - no-blocking-findings
  - attributed-issue-api-preserves-existing-provenance-path
files_reviewed:
  - actions/setup/js/create_issue.cjs
  - actions/setup/js/issue_body.cjs
  - actions/setup/js/index.cjs
  - pkg/workflow/safe_jobs.go
  - .github/scripts/aw_issue_clustering_publish.cjs
  - .github/workflows/aw-issue-clustering.md
comment_count: 0

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt54 · 63.7 AIC · ⌖ 7.29 AIC · ⊞ 19.4K · ◷
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, and logSpan.
  • 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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impeccable review — mixed_unclear (compiler env-var wiring + JS runtime API)

No blocking issues found. Verified:

  • buildJobLevelSafeOutputEnvVars wiring in safe_jobs.go correctly sets job-level GH_AW_* attribution env vars for custom safe-jobs; TestBuildSafeJobsAttributionMetadata passes (go test ./pkg/workflow/... -run TestBuildSafeJobs).
  • createIssue/formatIssueBody extraction in create_issue.cjs/issue_body.cjs preserves existing footer/marker generation exactly, including the trusted aw-essential-meta marker embedded by the clustering script before attribution is appended.
  • index.cjs is 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

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() reuses formatIssueBody from 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, via tryEnforceArrayLimit) that the built-in main() 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.cjs suite 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 — formatIssueBody is now shared verbatim between createIssue and main(), 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.cjs is 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 };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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");
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/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-issue handler, createIssue does not sanitize or cap labels/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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the createIssue API contract to document sanitized labels and the shared label/assignee limits. Committed as 3022404.

@pelikhan

pelikhan commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator Author

@copilot run pr-finisher skill

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

@copilot run pr-finisher skill

Ran the PR finisher. The review findings are fixed in 3022404, and I replied to each thread. Local validation passed; the previous concurrency-test failure also reproduces on base 2a19b2d. The review threads remain unresolved because the available tools could not resolve them, and the PR is still blocked with a check in progress.

@pelikhan
pelikhan merged commit 44af922 into main Oct 4, 2026
1 check passed
@pelikhan
pelikhan deleted the pelikhan-custom-safe-output-api branch October 4, 2026 17:58
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.91.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants