[FEATURE] Add sync-issue-fields composite action - #67
Open
John McCall (lowlydba) wants to merge 9 commits into
Open
[FEATURE] Add sync-issue-fields composite action#67John McCall (lowlydba) wants to merge 9 commits into
John McCall (lowlydba) wants to merge 9 commits into
Conversation
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]>
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]>
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]>
John McCall (lowlydba)
marked this pull request as ready for review
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]>
There was a problem hiding this comment.
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
PATCHupdatingtypeand/orissue_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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Generalizes the inline issue-form-to-org-field sync workflow drafted in
tf-data-platformPR #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 issuetypeand the OvertureMaps org'sScopeissue field, both in a singlePATCH /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:
type/scopeform answers at all (free-form issue, unrelated template): logs and returns before calling the API.PATCHcall is wrapped intry/catchand warns instead of failing the job.Field ID is hardcoded, not looked up by name
PATCH .../issues/{n}needs a numericfield_id, and resolving a name to that ID means callingGET /orgs/{org}/issue-fields— which only accepts classic PATs/OAuth tokens withread:org, neverGITHUB_TOKEN. Since this action is OvertureMaps-only and field IDs are stable once created,scope-org-field-iddefaults directly to the org'sScopefield (44814929, found viagh api orgs/OvertureMaps/issue-fields). That means the whole action runs ongithub-tokenalone, no second credential needed.Testing
action.ymlparses as YAML; each embeddedgithub-scriptblock was extracted, wrapped in anasync function, and passednode --check.Live-verified the actual write path against a throwaway issue (#68, now closed):
PATCH .../issues/68withtype: Bugandissue_field_values: [{field_id: 44814929, value: Base}]applied both in one call, and the response'stype.name/issue_field_values[].issue_field_idread back exactly as the skip-if-already-set checks expect. Didn't get a full in-Actions run (GitHub only letsworkflow_dispatchtarget workflows already registered on the default branch, so exercising it live would've meant merging a throwaway workflow tomainfirst) — the parser's field-name-to-key mapping (### Type→type) was confirmed by readinggithub-issue-parser's source instead of a live run.Not touching
tf-data-platformhere, that repo's draft PR will be updated separately to call this action instead of its inline workflow.