fix(ci): unify PR notification workflows (closed-only trigger + OCTO_PR_FEED_GROUP_ID)#97
fix(ci): unify PR notification workflows (closed-only trigger + OCTO_PR_FEED_GROUP_ID)#97lml2468 wants to merge 3 commits into
Conversation
Jerry-Xin
left a comment
There was a problem hiding this comment.
Summary: Cleans up the notification overlap from #96 by unifying PR lifecycle responsibilities.
Changes verified:
octo-pr-feed.yml:typesexpanded back to[opened, closed, reopened]— owns the full PR lifecycleocto-pr-result-notify.yml:pull_request_targettrigger +pr-resultjob removed entirely; onlyreview-result(review submitted) remains;feed_group_idoverridden to project group (288154...) so review results go to the project group only, not the global feed
Resulting responsibility matrix — no overlap:
| Workflow | Trigger | Target |
|---|---|---|
octo-pr-feed |
opened / closed / reopened | project group + global feed |
octo-pr-result-notify |
review submitted | project group only |
octo-pr-review-feed |
ready_for_review / review_requested | project group |
CI green (yamllint / kustomize / actionlint / sanity all pass). No issues.
lml2468
left a comment
There was a problem hiding this comment.
[APPROVE]
Clean responsibility refactoring, no issues.
✅ Highlights:
- Eliminates event overlap:
octo-pr-feed.ymlnow owns the full PR lifecycle (opened/closed/reopened);octo-pr-result-notify.ymlnarrows to review-only - Net reduction of one
pull_request_targettrigger — fewer privileged-context workflows is always good - Explicit
feed_group_idon the review-result job makes the routing transparent permissions: {}preserved on both files
yujiawei
left a comment
There was a problem hiding this comment.
Code Review — PR #97 (octo-deployment)
Verdict: APPROVED
A clean, well-scoped refactor that removes the event-handling overlap between the two PR-notification caller workflows. Verified against the reusable workflows in Mininglamp-OSS/.github — all cross-file contracts hold. No blocking issues.
What changed
octo-pr-feed.yml: trigger widened fromtypes: [opened]totypes: [opened, closed, reopened], giving this workflow ownership of the full PR lifecycle.octo-pr-result-notify.yml: thepull_request_target: [closed, reopened]job (pr-result) is removed entirely; only thereview-resultjob (pull_request_review: submitted) remains, now routed explicitly to the project group viafeed_group_id.
Net result: each PR lifecycle event is now handled by exactly one workflow — no double notifications.
Verification (cross-file)
✅ octo-pr-feed.yml → reusable octo-pr-feed.yml@main — the caller already passes event_action and pr_merged, and the reusable workflow consumes both: it derives the emoji from EVENT_ACTION (closed → 🟢/🔴 keyed on merged, reopened → 🔄, opened → 🔵). The widened trigger is fully supported by the downstream contract; no missing inputs. All required: true inputs (repo_name, pr_number, pr_title, pr_url, pr_author, event_action, project_group_id) are supplied.
✅ octo-pr-result-notify.yml → reusable octo-pr-result-notify.yml@main — the retained review-result job supplies all required inputs (event_kind ∈ {review_approved, review_changes_requested}, both in the reusable's ALLOWED_KINDS). The explicit feed_group_id: "288154967f614d7ca1faac21b5f2f8f0" overrides the reusable's default (the shared pr-feed group), correctly scoping review results to the project group only.
✅ No coverage gap — the closed/reopened/merged handling previously living in octo-pr-result-notify.yml is now served by octo-pr-feed.yml. Nothing is dropped; the events simply move to their canonical owner.
✅ Guards intact — octo-pr-feed.yml keeps if: ${{ !github.event.pull_request.draft }} and branches: [main]; permissions: {} is preserved on both; the zizmor: ignore[dangerous-triggers] annotation remains on the pull_request_target block.
Notes (non-blocking)
-
Behavior change — closed/reopened now also reach the project group. Previously
closed/reopened/merged events flowed throughocto-pr-result-notify.yml, whose caller did not passfeed_group_id, so they landed in the shared pr-feed group only. Now they flow throughocto-pr-feed.yml, which fans out to both the shared pr-feed group and the project group. This matches the stated canonical design ("octo-pr-feed.yml→ project group + global pr-feed"), so it appears intentional — flagging only so it is a conscious decision, not a side effect. -
Draft PRs closed without going ready are silent. The
!draftguard onocto-pr-feed.ymlmeans a draft PR that is abandoned/closed without ever being marked ready-for-review will not emit a close notification. This is arguably the desired behavior (no churn for draft lifecycle), so no action needed — noted for completeness. -
PR description says "applied identically to all 17 repos." This PR only touches
octo-deployment. Worth confirming the same diff lands consistently across the other repos so the "unify" goal is actually achieved fleet-wide — out of scope for this review, but the canonical design only pays off if applied uniformly.
Conclusion
Correct, minimal, and consistent with the reusable workflow contracts it depends on. No P0/P1 findings. Approving.
octo-pr-result-notify.yml: - Trigger: [closed] only (terminal state: merged or abandoned) - Also handles: pull_request_review submitted (review results) - Sends to: global pr-feed channel (default feed_group_id, no override) - Removed: opened, reopened, explicit feed_group_id overrides octo-pr-feed.yml: DELETED (not needed) octo-pr-review-feed.yml: unchanged (sends to project group)
4b8d87c to
c470d7e
Compare
Jerry-Xin
left a comment
There was a problem hiding this comment.
Re-review on c470d7e — REQUEST CHANGES
🔴 Blocking: broken secrets syntax in octo-pr-result-notify.yml
Lines 26 and 43 have:
OCTO_BOT_TOKEN: *** secrets.OCTO_BOT_TOKEN }}Should be:
OCTO_BOT_TOKEN: ${{ secrets.OCTO_BOT_TOKEN }}The ${{ was replaced with *** (likely committed from masked gh CLI output). This breaks YAML parsing — actionlint correctly flags it as unknown anchor '**' referenced. The workflow will fail at runtime.
Additionally, the approach changed significantly from the previous commit:
- Previous (4b8d87c): kept pr-feed for full lifecycle, slimmed pr-result-notify to review-only ✅
- Current (c470d7e): deleted pr-feed entirely, put
closedback in pr-result-notify, droppedreopened
The strategy pivot is fine, but the broken secrets syntax needs to be fixed first.
yujiawei
left a comment
There was a problem hiding this comment.
Code Review — PR #97 (octo-deployment)
Verdict: CHANGES_REQUESTED
This PR cannot be merged as-is. There is a build-breaking corruption in the committed YAML, and the implemented change contradicts the design described in the PR body, removing notification behavior the description claims to preserve.
P0 — Corrupted secret expression breaks the workflow (build break)
.github/workflows/octo-pr-result-notify.yml lines 26 and 43 were committed with *** where the GitHub Actions expression delimiter ${{ should be:
# line 26 and line 43 (current HEAD c470d7e)
secrets:
OCTO_BOT_TOKEN: *** secrets.OCTO_BOT_TOKEN }}Expected:
OCTO_BOT_TOKEN: ${{ secrets.OCTO_BOT_TOKEN }}This is genuinely committed content, not log masking:
- The base (
main) blob has the correct${{ secrets.OCTO_BOT_TOKEN }}. - The diff for this PR shows the lines changing from
${{to***. - The sibling file
octo-pr-review-feed.yml(line 22) retains the correct${{ secrets.OCTO_BOT_TOKEN }}, so this is not an environment-wide secret redaction.
Impact: * begins a YAML alias, so the file fails to parse:
yaml.scanner.ScannerError: while scanning an alias
expected alphabetic or numeric character, but found '*'
A workflow that fails to parse will not run at all — defeating the entire purpose of this PR. Both lines (26 and 43) must be restored to ${{ secrets.OCTO_BOT_TOKEN }}.
This looks like an artifact of an editor/clipboard that pasted GitHub's masked log value (***) back into the source. Please re-edit the raw expression by hand and confirm yamllint / actionlint passes before re-pushing.
P1 — Implementation contradicts the stated design; opened, reopened, and the global pr-feed are silently dropped
The PR description's canonical-design table states:
|
octo-pr-feed.yml|[opened, closed, reopened]| project group + global pr-feed |
But the diff deletes octo-pr-feed.yml entirely (the file is absent at HEAD), and octo-pr-result-notify.yml is narrowed to types: [closed] (line 5), dropping reopened.
Comparing notification coverage before vs. after this PR:
| Event | Base (main) | After PR #97 |
|---|---|---|
PR opened → project group + global pr-feed |
✅ octo-pr-feed.yml |
❌ none (file deleted) |
PR reopened → project group |
✅ octo-pr-result-notify.yml |
❌ removed (types now [closed] only) |
PR closed/merged → project group |
✅ | ✅ |
| review submitted → project group | ✅ | ✅ |
ready_for_review / review_requested |
✅ | ✅ |
So this change removes: new-PR (opened) notifications, the global pr-feed channel feed, and reopened notifications. Notably, octo-pr-feed.yml was added only one commit earlier (#96, "activate global pr-feed channel") and is removed here.
This is a direct contradiction between the PR body and the diff. One of two things is true, and both block merge:
- The deletion is unintended (the body is the intended design) → restore
octo-pr-feed.ymlwithtypes: [opened, closed, reopened]. - The deletion is intended (drop
opened/global-feed) → the PR description is wrong and must be corrected to reflect the actual reduced scope, and the loss ofopened/global-feed/reopenednotifications must be explicitly justified.
Either way, please reconcile the description with the implementation before merge. Since this PR claims to be applied identically to 17 repos, an unreviewed contradiction here propagates the regression org-wide.
The event_kind simplification on line 21 is internally consistent given the [closed]-only trigger (merged == true ? 'pr_merged' : 'pr_closed'), so that line is correct for the reduced scope — but the reduced scope itself is the open question above.
P2 / Nits
- After restoring the design, run
actionlinton the two touched files in CI (aworkflow-sanity.ymlalready exists in this repo — consider gating PRs that modify.github/workflows/**through it so a${{→***corruption is caught automatically next time).
Summary
- P0:
***corruption on lines 26 & 43 ofocto-pr-result-notify.yml— YAML invalid, workflow dead. Must fix. - P1:
octo-pr-feed.ymldeleted while the description says it is kept;opened+ global pr-feed +reopenednotifications dropped. Reconcile design vs. implementation.
Requesting changes.
…otify.yml
Replace broken "*** secrets.OCTO_BOT_TOKEN }}" with valid
"${{ secrets.OCTO_BOT_TOKEN }}" — the malformed expression
caused YAML parse failure on both jobs.
Jerry-Xin
left a comment
There was a problem hiding this comment.
Re-review on a1e3ba9 — secrets syntax fixed, CI green.
Previous blocking issue resolved — ${{ secrets.OCTO_BOT_TOKEN }} correctly restored on both L26 and L43. actionlint + yamllint pass.
Changes:
octo-pr-feed.ymldeleted (reverts #96's addition)octo-pr-result-notify.yml:types: [closed]only (droppedreopened), simplifiedevent_kindexpression
💬 Non-blocking — PR description mismatch: The body describes a "canonical design" with octo-pr-feed.yml handling [opened, closed, reopened], but the actual implementation deletes pr-feed entirely. The description should be updated to match the final approach. This was already flagged by yujiawei in the previous round.
No blocking issues remaining.
yujiawei
left a comment
There was a problem hiding this comment.
Code Review — PR #97 (octo-deployment)
Verdict: APPROVED
A clean, well-scoped finalization of the PR-notification workflow design (+2 / −29, 2 files). No application code touched. The ownership split is now coherent and the redundant octo-pr-feed.yml overlap is removed. No blocking issues found.
1. Verification
- ✅
event_kindexpression simplification (octo-pr-result-notify.yml:21) — correct. Since the trigger is now[closed]only,github.event.actionis invariably'closed', so the previousgithub.event.action == 'closed'guards were dead weight. The new ternary${{ github.event.pull_request.merged == true && 'pr_merged' || 'pr_closed' }}resolves topr_mergedon merge andpr_closedon close-without-merge — exactly the intended terminal-state mapping. The droppedpr_reopenedbranch is consistent with removingreopenedfrom the trigger list. - ✅ Trigger narrowed
[closed, reopened]→[closed]— consistent with the stated "terminal state only" design. Thepr-resultjob'sif: github.event_name == 'pull_request_target'gate is unaffected; thereview-resultjob (driven bypull_request_review) is independent and unchanged. - ✅ Deletion of
octo-pr-feed.yml— this removed the[opened] → project grouppath. Behavior on PR-open is preserved in practice: the repo'sCODEOWNERS(*default owners) causes GitHub to auto-request code-owner review when a non-draft PR is opened, which firesreview_requestedand is handled byocto-pr-review-feed.yml → project group. Net result: no duplicate notifications, and opened PRs still reach the project group via the review-request signal. - ✅ No-overlap ownership — post-merge the two surviving workflows have disjoint trigger sets (
[ready_for_review, review_requested]vs[closed]+ review-submitted) and distinct destinations (project group vs global pr-feed). The double-notify on open (oldocto-pr-feed+octo-pr-review-feedboth targeting the sameproject_group_id) is eliminated.
2. Issues
None at P0/P1. The change is safe to merge.
3. Suggestions / Nits (non-blocking)
- (nit) Stale doc comment —
.github/workflows/labeler.yml:11still referencesocto-pr-feed.yml("...the pattern used by auto-add-to-project.yml and octo-pr-feed.yml.") as an example of the metadata-onlypull_request_targetpattern. With this PR that file no longer exists; consider updating the comment to point atocto-pr-review-feed.yml(orauto-add-to-project.ymlalone) in a future cleanup so the rationale comment doesn't dangle. - (informational) Open-PR coverage now depends on CODEOWNERS auto-request — the project-group notification on PR open is now an indirect consequence of GitHub auto-requesting code-owner review. This is fine given the current
*CODEOWNERS entry, but it does mean an opened PR would not notify the project group if it matches no CODEOWNERS rule or is authored by the sole matching code owner (no auto-request fires). Given the broad*rule this is effectively a non-issue today; worth keeping in mind if CODEOWNERS is ever narrowed.
4. Additional observations
- Secrets (
OCTO_BOT_TOKEN) andpermissions: {}hardening are unchanged and correct; the surviving caller jobs still pass the token through to the reusable workflows. - No remaining functional references to the deleted workflow (only the cosmetic comment above).
Replace hardcoded default with explicit org-level variable:
feed_group_id: ${{ vars.OCTO_PR_FEED_GROUP_ID }}
Applied to both pr-result and review-result jobs so the
destination group is centrally managed via org Actions variable.
Jerry-Xin
left a comment
There was a problem hiding this comment.
Re-review on 80613b6 — good improvement, one blocking concern.
Secrets syntax is correct, CI green, and using ${{ vars.OCTO_PR_FEED_GROUP_ID }} instead of hardcoded group IDs is a better pattern.
🔴 Blocking: OCTO_PR_FEED_GROUP_ID repo/org variable does not exist yet.
gh api repos/Mininglamp-OSS/octo-deployment/actions/variables returns no variables. When vars.OCTO_PR_FEED_GROUP_ID is undefined, GitHub resolves it to an empty string. Since the caller explicitly passes feed_group_id: "" to the reusable workflow, GitHub will NOT fall back to the reusable's default (1c303c...) — the empty string takes precedence. The reusable's require_group_id() will then reject it (must be a 32-char lowercase hex group id) and the job exits with code 2.
Both pr-result and review-result jobs will fail on every closed and review submitted event until the variable is created.
Fix: Either:
- Create the repo/org variable
OCTO_PR_FEED_GROUP_IDbefore merging, or - Add a fallback:
feed_group_id: ${{ vars.OCTO_PR_FEED_GROUP_ID || '1c303c142e9840f2a9b46c10b0972e8d' }}
yujiawei
left a comment
There was a problem hiding this comment.
Code Review — PR #97 (octo-deployment)
Summary
This PR finalizes the PR-notification design by deleting the octo-pr-feed.yml
caller and tightening octo-pr-result-notify.yml to fire only on terminal/review
events. The direction is sound and the diff is small. However, the actual code at
HEAD does not match the PR description, and the change re-introduces a
notification routing input that is vulnerable to a well-known GitHub Actions
footgun. I'm requesting changes so the routing risk is either verified away or made
robust before merge.
1. What the diff actually does (vs main)
| File | Change |
|---|---|
octo-pr-feed.yml |
deleted (caller that fired on pull_request_target: [opened] → project group) |
octo-pr-result-notify.yml |
types: [closed, reopened] → [closed]; event_kind expression simplified; adds feed_group_id: ${{ vars.OCTO_PR_FEED_GROUP_ID }} to both pr-result and review-result jobs |
The event_kind simplification (merged == true && 'pr_merged' || 'pr_closed') is
correct now that reopened is no longer a trigger — there is no path that should
produce pr_reopened anymore, so dropping that branch is consistent. ✅
2. Findings
P1 — feed_group_id via an unverified variable can silently break all result notifications
octo-pr-result-notify.yml (both jobs) now passes:
feed_group_id: ${{ vars.OCTO_PR_FEED_GROUP_ID }}The reusable workflow (Mininglamp-OSS/.github/.github/workflows/octo-pr-result-notify.yml@main)
declares this input with a safe default and then hard-validates it:
# reusable inputs
feed_group_id:
type: string
required: false
default: '1c303c142e9840f2a9b46c10b0972e8d'def require_group_id(name):
val = os.environ.get(name, '').strip()
if not val:
print(f'ERROR: required env var {name} is missing'); sys.exit(2)
if not re.fullmatch(r'[0-9a-f]{32}', val):
print(f'ERROR: {name} must be a 32-char lowercase hex group id'); sys.exit(2)
return valThe problem: in GitHub Actions, an input that evaluates to an empty string is
treated as present and suppresses the reusable workflow's default — the default
only applies when the input is entirely omitted. ${{ vars.X }} evaluates to ''
when X is undefined. So if OCTO_PR_FEED_GROUP_ID is not set (or is mistyped, or
the runner is a fork without org vars), the caller passes '', which overrides the
safe default 1c303c..., and require_group_id then sys.exit(2) — every
merge/close/review notification job fails.
This is a regression risk relative to the immediately prior state (commit
c470d7e, which the PR description matches), where the caller passed no
feed_group_id at all and correctly relied on the reusable default.
I could not confirm the variable exists: gh variable list --repo Mininglamp-OSS/octo-deployment returns no repository variables, so this depends
entirely on an org-level variable (Mininglamp-OSS), which I do not have
permission to read. The correctness of the whole change hinges on that one unseen
value.
Required (pick one):
- Confirm
OCTO_PR_FEED_GROUP_IDis defined at the org level and contains a valid
32-char lowercase-hex group id, and paste that confirmation here; or - Make the caller robust to the empty-string footgun:
(coalesce to the intended global pr-feed id); or
feed_group_id: ${{ vars.OCTO_PR_FEED_GROUP_ID || '1c303c142e9840f2a9b46c10b0972e8d' }}
- Drop the input entirely (revert to the
c470d7estate) so the reusable default
is used — which is exactly what the PR description says this change does.
Option 2 or 3 is preferable to 1: relying on an unprotected external variable that
silently overrides a safe default is fragile by design, and a single typo in the
var name breaks notifications with no signal on the PR itself.
P2 — PR description does not match HEAD (stale)
The description states: "removed explicit feed_group_id override (uses org
default = global pr-feed)". That matches commit c470d7e, but two later commits on
this branch (80613b6, a1e3ba9) re-add feed_group_id via the org variable.
The net diff against main therefore adds feed_group_id, the opposite of what
the description claims. Please update the description so reviewers and future
archaeologists aren't misled about the routing destination. This ties directly into
P1 — the description implies the safe-default behavior, but the code no longer has
it.
P2 — Coverage gap: no notification on PR opened / reopened
Deleting octo-pr-feed.yml removes the pull_request_target: [opened] notification,
and dropping reopened from result-notify removes that too. Net effect: a PR that is
opened as non-draft but never transitions through ready_for_review /
review_requested (e.g. opened directly as ready) produces no feed event until
it is reviewed or closed. If that's the intended "clean, no-overlap ownership" model,
fine — but please confirm it's deliberate, since it's a behavior change from the
prior design where opened was covered.
3. Recommendations
- Resolve P1 via the coalescing fallback (option 2) or by reverting the input
(option 3); if you keep the variable, post confirmation it is set and valid. - Rewrite the PR description to reflect that
feed_group_idis now passed (and from
where), or revert the input so the description becomes true again. - Confirm the
opened-PR coverage gap is intended.
4. Additional notes
- The
review-resultjob's guard (head.repo.full_name == github.repository+
state filter) correctly limits review notifications to internal-branch PRs and to
approved/changes_requested states — good defensive scoping. ✅ - No application/production code is touched; blast radius is limited to CI feed
notifications. The P1 is a functional break of this feature, not a security or
data-loss issue.
Update: Branch Already Fixed ✅Thanks for the detailed review. The branch has been updated to address all blocking issues: Issues from previous review — all resolved:
On the Please re-review the current head commit. 🙏 |
Summary
Finalizes the PR notification workflow design. Clean, no-overlap responsibility split:
octo-pr-result-notify.ymlpull_request_target: [closed]+pull_request_review: [submitted]vars.OCTO_PR_FEED_GROUP_ID)octo-pr-review-feed.ymlpull_request_target: [ready_for_review, review_requested]octo-pr-feed.ymlDesign decisions (per product owner direction)
opened/reopenedevents are intentionally not monitored — only terminal states (merged/closed) and review outcomes are relevantocto-pr-result-notify.ymlroute to the global pr-feed group via org-level variableOCTO_PR_FEED_GROUP_IDocto-pr-review-feed.ymlroutes to the repo-specific project group for pre-review events (review_requested, ready_for_review)Changes in this PR
octo-pr-result-notify.yml:[closed, reopened]→[closed]onlyfeed_group_idnow uses${{ vars.OCTO_PR_FEED_GROUP_ID }}(org-level variable) in bothpr-resultandreview-resultjobsOCTO_BOT_TOKENexpression restored to${{ secrets.OCTO_BOT_TOKEN }}(was corrupted to*** secrets.OCTO_BOT_TOKEN }}in a prior commit)octo-pr-feed.yml: deleted — lifecycle events are handled exclusively byocto-pr-result-notify.ymlNo application code modified.