Skip to content

[FEATURE] Add sync-issue-fields composite action - #67

Open
John McCall (lowlydba) wants to merge 9 commits into
mainfrom
lowlydba-sync-issue-fields-action
Open

[FEATURE] Add sync-issue-fields composite action#67
John McCall (lowlydba) wants to merge 9 commits into
mainfrom
lowlydba-sync-issue-fields-action

Conversation

@lowlydba

@lowlydba John McCall (lowlydba) commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Generalizes the inline issue-form-to-org-field sync workflow drafted in tf-data-platform PR #4629 into a reusable composite action, sync-issue-fields. Closes OvertureMaps/tf-data-platform#4625.

Given an issue form with Type/Scope-style dropdown answers, it writes the repo-level issue type and the OvertureMaps org's Scope issue field, both in a single PATCH /repos/{owner}/{repo}/issues/{issue_number}. Confirmed both fields are patchable together against the GitHub REST API OpenAPI spec, and both only need push access to the repo.

Follows check-linked-issue's conventions: composite action, actions/github-script + core.getInput(), pinned third-party SHAs, and a sibling README.

Skips a field if it's already set on the issue (doesn't clobber manual triage), and skips entirely if the issue has no form answers for either field.

Skips instead of guessing

GitHub has no way to trigger a workflow only for issues created from a specific template, so the action has to cope with issues it wasn't meant for:

  • No type/scope form answers at all (free-form issue, unrelated template): logs and returns before calling the API.
  • A field is already set on the issue (manual triage): dropped from the patch instead of overwritten.
  • A form answer's value doesn't match any enabled repo type or field option (a similar-looking template, or a typo): the PATCH call is wrapped in try/catch and warns instead of failing the job.

Field ID is hardcoded, not looked up by name

PATCH .../issues/{n} needs a numeric field_id, and resolving a name to that ID means calling GET /orgs/{org}/issue-fields — which only accepts classic PATs/OAuth tokens with read:org, never GITHUB_TOKEN. Since this action is OvertureMaps-only and field IDs are stable once created, scope-org-field-id defaults directly to the org's Scope field (44814929, found via gh api orgs/OvertureMaps/issue-fields). That means the whole action runs on github-token alone, no second credential needed.

Testing

action.yml parses as YAML; each embedded github-script block was extracted, wrapped in an async function, and passed node --check.

Live-verified the actual write path against a throwaway issue (#68, now closed): PATCH .../issues/68 with type: Bug and issue_field_values: [{field_id: 44814929, value: Base}] applied both in one call, and the response's type.name/issue_field_values[].issue_field_id read back exactly as the skip-if-already-set checks expect. Didn't get a full in-Actions run (GitHub only lets workflow_dispatch target workflows already registered on the default branch, so exercising it live would've meant merging a throwaway workflow to main first) — the parser's field-name-to-key mapping (### Typetype) was confirmed by reading github-issue-parser's source instead of a live run.

Not touching tf-data-platform here, that repo's draft PR will be updated separately to call this action instead of its inline workflow.

Generalizes the inline issue-form-to-org-field sync workflow drafted in
tf-data-platform PR #4629 into a reusable composite action. Given an issue
form with Type/Scope-style dropdown answers, it writes the repo-level issue
type and looks up an org-level issue field by name (field IDs differ per
org) to write the scope answer, in a single PATCH. Skips fields already set
by manual triage, and skips entirely if the issue has no form answers.

Co-authored-by: Copilot App <[email protected]>

Signed-off-by: John McCall <[email protected]>
GET /orgs/{org}/issue-fields only accepts classic PATs/OAuth tokens with
read:org per GitHub's REST docs; GITHUB_TOKEN and fine-grained PATs aren't
listed as supported at all. Confirmed the repo-scoped default token can
never work here, regardless of workflow permissions.

Adds org-fields-token as a lighter-weight alternative to standing up a
GitHub App just for this one read, and updates the README to state the
403 as a confirmed platform limitation rather than an open question.

Co-authored-by: Copilot App <[email protected]>

Signed-off-by: John McCall <[email protected]>
GET /orgs/{org}/issue-fields only accepts classic PATs/OAuth tokens with
read:org, never GITHUB_TOKEN. Rather than shipping a whole second credential
(PAT or GitHub App) to resolve a field name to its numeric ID, hardcode
scope-org-field-id to the OvertureMaps org's Scope field (44814929, found
via gh api orgs/OvertureMaps/issue-fields) since this action is org-specific
and field IDs are stable once created.

Drops org-fields-token, org-fields-client-id, org-fields-private-key, and
scope-org-field-name entirely, along with the app-token/lookup steps that
existed only to serve them. The whole action now runs on github-token alone.
The already-set check now compares issue_field_id instead of the field name,
since the issue GET response includes it directly.

Co-authored-by: Copilot App <[email protected]>

Signed-off-by: John McCall <[email protected]>
Adds a 'Why this exists' section: form dropdown answers are just text in
the issue body until something copies them onto the actual type/org fields
that drive search, board views, and reporting, and doing that by hand is
easy to skip at high issue volume.

Co-authored-by: Copilot App <[email protected]>

Signed-off-by: John McCall <[email protected]>
Live-verified the PATCH .../issues/{n} call against a throwaway issue
(#68, now closed): type and issue_field_values applied
together in one request, matching the shape sync-issue-fields sends and
confirming the skip-if-already-set checks (issue.type.name,
issue_field_values[].issue_field_id) read back correctly.

Co-authored-by: Copilot App <[email protected]>

Signed-off-by: John McCall <[email protected]>
…nsumer

parse-issue-form fetches and parses an issue's form answers into structured
outputs (answers, type-name, field-values), with no field-specific logic of
its own. sync-issue-fields now calls it and only handles deciding what to
patch and applying the patch, so future issue-form automations (auto-assign,
auto-label, etc.) reuse the fetch-and-parse step instead of reimplementing it.

Also hardens the wrong-template case: GitHub has no way to trigger only on a
specific issue template, so a non-standard template can produce a type/scope-
shaped answer whose value the API rejects (422). The patch call now warns and
continues instead of failing the job.

Co-authored-by: Copilot App <[email protected]>

Signed-off-by: John McCall <[email protected]>
@lowlydba John McCall (lowlydba) changed the title [FEATURE] Add sync-issue-fields composite action [FEATURE] Add parse-issue-form and sync-issue-fields composite actions Jul 30, 2026
parse-issue-form was just fetch-issue plus a call to stefanbuck/github-issue-parser,
no logic of its own. Not worth a second action until there's an actual second
consumer of the fetch-and-parse step, so folding it back into sync-issue-fields
as inline steps. Keeps the graceful-skip and warn-on-invalid-value handling
from the split.

Co-authored-by: Copilot App <[email protected]>

Signed-off-by: John McCall <[email protected]>
@lowlydba John McCall (lowlydba) changed the title [FEATURE] Add parse-issue-form and sync-issue-fields composite actions [FEATURE] Add sync-issue-fields composite action Jul 30, 2026
@lowlydba
John McCall (lowlydba) marked this pull request as ready for review July 30, 2026 17:48
@lowlydba
John McCall (lowlydba) requested a review from a team as a code owner July 30, 2026 17:48
Copilot AI review requested due to automatic review settings July 30, 2026 17:48
Interpolating steps.fetch-issue.outputs.issue-number directly into the script
string let zizmor flag it as a template-injection risk: any output-derived
value expanded into the script text is attacker-controllable if that output
ever contains untrusted content. Passed it through env/process.env like the
step's other values instead, zizmor now reports no findings.

Co-authored-by: Copilot App <[email protected]>

Signed-off-by: John McCall <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a reusable composite GitHub Action (sync-issue-fields) to sync issue-form dropdown answers into the repo-level issue type and an org-level issue field, while avoiding overwriting values already set by manual triage.

Changes:

  • Added a new composite action that fetches an issue, parses issue-form answers, and conditionally applies a single REST API PATCH updating type and/or issue_field_values.
  • Added documentation with example workflow usage, configuration options, and rationale for hardcoding the org field ID.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
.github/actions/sync-issue-fields/action.yml Implements the composite action logic (fetch, parse, decide updates, PATCH).
.github/actions/sync-issue-fields/README.md Documents setup, inputs, permissions, and behavior/edge-case handling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/actions/sync-issue-fields/action.yml
Comment thread .github/actions/sync-issue-fields/README.md
Comment thread .github/actions/sync-issue-fields/action.yml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants