Skip to content

feat(workflows): release-notify + medal-bumper reusable workflows - #1

Open
alioftech wants to merge 2 commits into
mainfrom
feat/release-notify-and-medal-bumper
Open

feat(workflows): release-notify + medal-bumper reusable workflows#1
alioftech wants to merge 2 commits into
mainfrom
feat/release-notify-and-medal-bumper

Conversation

@alioftech

Copy link
Copy Markdown
Contributor

Summary

Two reusable workflows that other Medal-Social repos consume via uses::

  • release-notify.yml — POSTs release notifications on push: prod to Slack
    (#releases via the Medal Releases Slack app + chat/@chat-adapter/slack),
    Google Chat (the Releases space via the Medal Releases GChat app +
    chat/@chat-adapter/gchat), and Sanity (NextMedal's collection.changelog
    via @sanity/client). Per-channel adapter steps gate on channel name + secret
    presence; failures log + continue (a paused destination must not block release).
  • medal-bumper.yml — Pattern B agentic upgrade workflow. Daily mode bumps
    infra deps (Node toolchain — vitest, biome, typescript, tsx, eslint,
    prettier, @types/node); weekly mode bumps everything else. Each candidate
    is bumped + tested (pnpm test && typecheck && build); green bumps bundle
    into one PR, red bumps get isolated single-dep PRs with the failure log.
    Claude (via ANTHROPIC_API_KEY) writes the PR description.
  • example-consumer.yml — reference-only file documenting the consumer-side
    opt-in pattern.

Adapter scripts live in scripts/ and run under Node 24 with
--experimental-strip-types (no transpile). The Slack and GChat scripts share
lib/release-card.ts as a single Card JSX source — the chat package serializes
the same Card to Block Kit and Cards v2.

Required org secrets (manual setup before consumers can succeed)

  • SLACK_RELEASES_BOT_TOKEN — bot token from the Medal Releases Slack app
  • GCHAT_RELEASES_BOT_TOKEN — service-account JSON from the Medal Releases GChat app
  • SANITY_RELEASES_TOKEN — write-scoped token from NextMedal's Sanity project
  • SANITY_PROJECT_ID — NextMedal Sanity project ID
  • SANITY_DATASET — production dataset name (typically production)
  • ANTHROPIC_API_KEY — for Medal Bumper PR descriptions

See README.md for the full setup walkthrough including the manual Slack + GChat
app-creation steps.

Source-of-truth docs

medal-social-best-practices/bots/ (in the hacks repo) — was updated in the
preceding hacks-main commit to reflect the final v1 design (three destinations +
hybrid Renovate-narrow + Medal Bumper). This PR is the implementation of that spec.

Validation plan

After this PR merges and the org secrets are set:

  1. Open a follow-up PR to pilot-talk adding the consumer-side opt-in workflows
    (release-notify, medal-bumper) + the security-only renovate.json.
  2. Trigger a real prod release on pilot-talk; verify a message lands in Slack
    #releases, the Releases GChat space, and a new collection.changelog
    document in NextMedal Sanity.
  3. Wait 24h for Medal Bumper's first daily run; expect a PR with infra-dep bumps
    if any are outdated.
  4. After 1 week of stability, fan out to other repos (meda, MedalSocial-SDK,
    pilot, pencil-canvas, Picasso, medal-monorepo, NextMedal, auto-cs)
    per medal-social-best-practices/bots/README.md.

Test plan

  • Reviewer reads README.md and confirms the setup flow is unambiguous.
  • Reviewer skims the three notify scripts and confirms the chat-package
    usage matches auto-cs/lib/notifications/release.ts (the in-prod pattern).
  • Reviewer skims medal-bumper.ts and confirms the bundling logic
    (greens → 1 PR, reds → N PRs) matches the spec.
  • After merge: org admin sets the six secrets per the README.
  • After secrets: open the pilot-talk consumer PR.

Don't merge until

  • An org admin commits to setting the manual app-creation flows (Slack +
    GChat) within ~24h of merge, otherwise the workflows will silently no-op
    (which is intentional — if: secret-present guards — but still defeats the
    point of merging the implementation).

Two reusable workflows that other Medal-Social repos consume via `uses:`:

- release-notify.yml — Triggered on push:prod from a calling repo. Diffs
  HEAD~1..HEAD, extracts changeset/CHANGELOG content, builds a canonical
  payload, and dispatches it to up to three destinations:
    * Slack via the "Medal Releases" Slack app + chat + @chat-adapter/slack
    * Google Chat via "Medal Releases" GChat app + chat + @chat-adapter/gchat
    * Sanity via @sanity/client → NextMedal's collection.changelog
  Per-channel adapter steps gate on (a) channel name in inputs.channels AND
  (b) the relevant secret being non-empty. Failures log + continue (a paused
  destination must not block the release pipeline).

- medal-bumper.yml — Pattern B agentic upgrade workflow modeled on the
  pilot/MedalSocial-SDK auto-changeset flow. Daily mode bumps infra deps
  (Node toolchain — vitest/biome/typescript/tsx/eslint/prettier/@types/node),
  weekly mode bumps everything else. Per candidate: bump → install →
  pnpm test && typecheck && build. Green bumps bundle into ONE PR per run;
  red bumps get isolated single-dep PRs with the failure log embedded.
  Claude (via ANTHROPIC_API_KEY) writes the PR description.

- example-consumer.yml — Reference-only docs file showing the consumer-side
  opt-in pattern.

Adapter scripts live in scripts/ and run under Node 24 with
--experimental-strip-types (no transpile step). The Slack and GChat scripts
share lib/release-card.ts as the single source of truth for the Card JSX,
serialized to Block Kit and Cards v2 by the chat package's adapters.

README.md documents:
- Workflow usage and inputs
- Required org-level secrets and how to set them
- Manual app-creation steps for Slack and Google Chat
- Source-of-truth pointer to medal-social-best-practices/bots/

Required org secrets (set manually before consumers can succeed):
SLACK_RELEASES_BOT_TOKEN, GCHAT_RELEASES_BOT_TOKEN, SANITY_RELEASES_TOKEN,
SANITY_PROJECT_ID, SANITY_DATASET, ANTHROPIC_API_KEY.
@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Add release-notify and medal-bumper reusable workflows with adapter scripts

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Adds two reusable GitHub Actions workflows for org-wide dependency management
  - release-notify.yml posts release notifications to Slack, Google Chat, and Sanity
  - medal-bumper.yml performs agentic dependency upgrades with AI-generated PR descriptions
• Implements TypeScript adapter scripts that run under Node 24 with native type stripping
  - Shared release-card.ts JSX component serialized to Block Kit and Cards v2
  - Supports three notification destinations with independent failure handling
• Includes comprehensive documentation and setup instructions for org secrets and manual app
  creation
• Provides reference example consumer workflow showing opt-in pattern for downstream repos
Diagram
flowchart LR
  A["Prod Release Push"] -->|triggers| B["release-notify.yml"]
  B -->|builds payload| C["Canonical Payload"]
  C -->|dispatches to| D["Slack Adapter"]
  C -->|dispatches to| E["GChat Adapter"]
  C -->|dispatches to| F["Sanity Adapter"]
  D -->|posts via chat| G["Slack #releases"]
  E -->|posts via chat| H["GChat Releases Space"]
  F -->|writes via client| I["Sanity changelog"]
  J["Daily/Weekly Schedule"] -->|triggers| K["medal-bumper.yml"]
  K -->|enumerates outdated| L["Dependency Candidates"]
  L -->|filters by mode| M["Infra or App Deps"]
  M -->|bump + test each| N["Green vs Red Results"]
  N -->|bundles greens| O["Single Batch PR"]
  N -->|isolates reds| P["Individual Failure PRs"]
  O -->|AI description| Q["Claude via Anthropic API"]
  P -->|AI description| Q
Loading

Grey Divider

File Changes

1. .github/workflows/release-notify.yml ✨ Enhancement +184/-0

Reusable workflow for release notifications

.github/workflows/release-notify.yml


2. .github/workflows/medal-bumper.yml ✨ Enhancement +96/-0

Reusable agentic dependency upgrade workflow

.github/workflows/medal-bumper.yml


3. .github/workflows/example-consumer.yml 📝 Documentation +56/-0

Reference documentation for consumer opt-in pattern

.github/workflows/example-consumer.yml


View more (7)
4. README.md 📝 Documentation +187/-0

Setup guide for workflows, secrets, and manual app creation

README.md


5. scripts/lib/release-card.ts ✨ Enhancement +65/-0

Shared JSX Card component for release notifications

scripts/lib/release-card.ts


6. scripts/notify-slack.ts ✨ Enhancement +44/-0

Slack adapter for release notifications via chat package

scripts/notify-slack.ts


7. scripts/notify-gchat.ts ✨ Enhancement +54/-0

Google Chat adapter for release notifications via chat package

scripts/notify-gchat.ts


8. scripts/notify-sanity.ts ✨ Enhancement +124/-0

Sanity adapter writing changelog documents via @sanity/client

scripts/notify-sanity.ts


9. scripts/medal-bumper.ts ✨ Enhancement +319/-0

Pattern B agentic upgrade script with Claude PR descriptions

scripts/medal-bumper.ts


10. scripts/package.json Dependencies +21/-0

Dependencies for adapter scripts and medal-bumper workflow

scripts/package.json


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Apr 27, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (6) 📘 Rule violations (0)

Grey Divider


Action required

1. Wrong release diff base 🐞 Bug ≡ Correctness
Description
release-notify.yml computes prev as HEAD~1 instead of using github.event.before, so
multi-commit pushes/merges to prod will generate payloads/compare links from the wrong base and
can incorrectly skip or misreport releases.
Code

.github/workflows/release-notify.yml[R87-100]

+          # First push to a branch — github.event.before is all-zeroes; bail.
+          if [[ "${{ github.event.before }}" == "0000000000000000000000000000000000000000" ]]; then
+            echo 'First push to this branch — no previous commit to diff against; skipping notify.'
+            echo 'skip=true' >> "$GITHUB_OUTPUT"
+            exit 0
+          fi
+
+          prev=$(git rev-parse HEAD~1 2>/dev/null || echo "")
+          curr=$(git rev-parse HEAD)
+          if [[ -z "$prev" ]]; then
+            echo 'No previous commit found; skipping notify.'
+            echo 'skip=true' >> "$GITHUB_OUTPUT"
+            exit 0
+          fi
Evidence
The workflow explicitly checks github.event.before for the all-zeroes first-push case, but then
ignores it and sets prev from HEAD~1, and uses that value for git diff and compareUrl
construction.

.github/workflows/release-notify.yml[87-143]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`release-notify.yml` diffs against `HEAD~1`, which is not the correct previous branch HEAD when a push contains multiple commits (common for merges). This can produce incorrect `changed` detection, incorrect summaries, and incorrect `compareUrl`.

### Issue Context
For `push` events, GitHub provides the previous branch head as `github.event.before`. That value should be used as the diff base.

### Fix Focus Areas
- .github/workflows/release-notify.yml[87-143]

### Suggested change
- Set `prev="${{ github.event.before }}"` and `curr="${{ github.sha }}"` (or `curr=$(git rev-parse HEAD)`), and ensure the commits are available (you already use `fetch-depth: 0`).
- Keep the all-zeroes guard for first-push and consider forced-push behavior explicitly if desired.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Command failures ignored 🐞 Bug ☼ Reliability
Description
medal-bumper.ts uses spawnSync for critical commands (pnpm up, git commit, git push, `gh
pr create`) but does not check exit codes, so it can print "Done" while failing to push branches or
create PRs (especially for red bumps where pnpm up may fail again).
Code

scripts/medal-bumper.ts[R152-203]

+  for (const g of greens) {
+    spawnSync('pnpm', ['up', `${g.name}@${g.to}`], { stdio: 'inherit' })
+  }
+  spawnSync('git', ['add', 'package.json', 'pnpm-lock.yaml'], { stdio: 'inherit' })
+
+  const commitMsg = `chore(deps): ${MODE} bump batch (${greens.length} packages)`
+  spawnSync('git', ['commit', '-m', commitMsg], { stdio: 'inherit' })
+  spawnSync('git', ['push', '-u', 'origin', branch], { stdio: 'inherit' })
+
+  const body = await writePrBodyForBatch(greens, MODE, REPO)
+  const bodyFile = path.join(mkdtempSync(path.join(tmpdir(), 'bumper-')), 'body.md')
+  writeFileSync(bodyFile, body, 'utf8')
+
+  spawnSync(
+    'gh',
+    [
+      'pr',
+      'create',
+      '--repo',
+      REPO,
+      '--base',
+      BASE_BRANCH,
+      '--head',
+      branch,
+      '--title',
+      commitMsg,
+      '--body-file',
+      bodyFile,
+      '--label',
+      'dependencies',
+      '--label',
+      'medal-bumper',
+    ],
+    { stdio: 'inherit' },
+  )
+}
+
+// ── 6. Isolate red bumps into individual PRs ───────────────────────────────
+
+for (const r of reds) {
+  console.log(`\n[bumper] Opening isolated PR for failing bump ${r.name}…`)
+  execSync(`git reset --hard ${baselineSha}`, { stdio: 'inherit' })
+
+  const branch = `medal-bumper/needs-attention-${r.name.replace(/[@\/]/g, '-')}-${todayStamp()}`
+  execSync(`git switch -c ${branch}`, { stdio: 'inherit' })
+
+  // Bump (don't run tests — the PR exists to surface the failure to humans).
+  spawnSync('pnpm', ['up', `${r.name}@${r.to}`], { stdio: 'inherit' })
+  spawnSync('git', ['add', 'package.json', 'pnpm-lock.yaml'], { stdio: 'inherit' })
+  spawnSync('git', ['commit', '-m', `chore(deps): bump ${r.name} to ${r.to} [needs attention]`], { stdio: 'inherit' })
+  spawnSync('git', ['push', '-u', 'origin', branch], { stdio: 'inherit' })
+
Evidence
Multiple critical operations are executed via spawnSync(...) without verifying status/error.
This makes failures non-fatal and can result in no PRs being created even though the script
completes.

scripts/medal-bumper.ts[152-187]
scripts/medal-bumper.ts[191-231]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Critical steps can fail silently because `spawnSync` return values aren’t checked. This can lead to skipped PRs, unpushed branches, or failed commits with no hard failure.

### Issue Context
This is especially problematic in the red-bump PR path: the script attempts `pnpm up` again and proceeds to `git commit/push` even if the bump didn’t apply.

### Fix Focus Areas
- scripts/medal-bumper.ts[152-187]
- scripts/medal-bumper.ts[191-231]

### Suggested change
- Wrap `spawnSync` in a helper like `runOrThrow(cmd, args, opts)` that throws on `status !== 0` or `error`.
- For red bumps, if the bump command fails, skip PR creation (or create a PR/issue that only contains logs) rather than attempting to commit empty/no-op changes.
- Consider switching the critical `spawnSync` calls to `execSync` when you want hard-fail semantics.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Untracked state not reset 🐞 Bug ☼ Reliability
Description
medal-bumper.ts uses git reset --hard between bump attempts, but that does not remove untracked
artifacts (e.g., node_modules/, build outputs, caches), so per-candidate test results can be
contaminated by previous attempts.
Code

scripts/medal-bumper.ts[R105-126]

+for (const [name, info] of candidates) {
+  console.log(`\n[bumper] Trying ${name}@${info.latest}…`)
+  // Reset working tree before each attempt to keep results independent.
+  execSync(`git reset --hard ${baselineSha}`, { stdio: 'inherit' })
+
+  const bump = spawnSync('pnpm', ['up', `${name}@${info.latest}`], { stdio: 'pipe', encoding: 'utf8' })
+  if (bump.status !== 0) {
+    console.log(`  ✗ pnpm up failed`)
+    results.push({
+      name,
+      from: info.current,
+      to: info.latest,
+      ok: false,
+      log: trimLog(bump.stdout + bump.stderr),
+    })
+    continue
+  }
+
+  const test = spawnSync('bash', ['-c', 'pnpm test && pnpm typecheck && pnpm build'], {
+    stdio: 'pipe',
+    encoding: 'utf8',
+  })
Evidence
The script claims attempts are independent but only resets tracked files; it never runs git clean
nor removes node_modules/artifacts between attempts, while repeatedly running installs/tests in
the same workspace.

scripts/medal-bumper.ts[105-136]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`git reset --hard` does not clean untracked files, so each candidate’s environment can carry over artifacts from the previous run (including `node_modules/`). This can lead to false greens/reds.

### Issue Context
The script runs many bump/test cycles in a single checkout.

### Fix Focus Areas
- scripts/medal-bumper.ts[105-136]

### Suggested change
After each `git reset --hard`, add a cleanup step such as:
- `execSync('git clean -fdx', { stdio: 'inherit' })`

or run each candidate in an isolated git worktree/temp directory so dependency installs and build outputs can’t leak across attempts.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Bundled bumps not re-tested 🐞 Bug ≡ Correctness
Description
After replaying all green bumps into the batch branch, medal-bumper.ts commits/pushes/opens the PR
without running pnpm test && pnpm typecheck && pnpm build on the combined set, so the bundled PR
can fail even though each bump passed alone.
Code

scripts/medal-bumper.ts[R138-187]

+// Reset and replay only the green bumps for the bundled PR.
+execSync(`git reset --hard ${baselineSha}`, { stdio: 'inherit' })
+
+const greens = results.filter((r) => r.ok)
+const reds = results.filter((r) => !r.ok)
+
+console.log(`\n[bumper] ${greens.length} green / ${reds.length} red`)
+
+// ── 5. Bundle green bumps into one PR ──────────────────────────────────────
+
+if (greens.length > 0) {
+  const branch = `medal-bumper/${MODE}-${todayStamp()}`
+  execSync(`git switch -c ${branch}`, { stdio: 'inherit' })
+
+  for (const g of greens) {
+    spawnSync('pnpm', ['up', `${g.name}@${g.to}`], { stdio: 'inherit' })
+  }
+  spawnSync('git', ['add', 'package.json', 'pnpm-lock.yaml'], { stdio: 'inherit' })
+
+  const commitMsg = `chore(deps): ${MODE} bump batch (${greens.length} packages)`
+  spawnSync('git', ['commit', '-m', commitMsg], { stdio: 'inherit' })
+  spawnSync('git', ['push', '-u', 'origin', branch], { stdio: 'inherit' })
+
+  const body = await writePrBodyForBatch(greens, MODE, REPO)
+  const bodyFile = path.join(mkdtempSync(path.join(tmpdir(), 'bumper-')), 'body.md')
+  writeFileSync(bodyFile, body, 'utf8')
+
+  spawnSync(
+    'gh',
+    [
+      'pr',
+      'create',
+      '--repo',
+      REPO,
+      '--base',
+      BASE_BRANCH,
+      '--head',
+      branch,
+      '--title',
+      commitMsg,
+      '--body-file',
+      bodyFile,
+      '--label',
+      'dependencies',
+      '--label',
+      'medal-bumper',
+    ],
+    { stdio: 'inherit' },
+  )
+}
Evidence
The script explicitly re-applies each green bump and then immediately stages/commits/pushes and
creates a PR; there is no test invocation after the combined upgrades are applied.

scripts/medal-bumper.ts[138-187]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The batch PR is created without verifying that all green bumps still pass when combined, which can happen due to transitive conflicts or peer dependency interactions.

### Issue Context
Individual testing occurs earlier, but the combined branch is a new state.

### Fix Focus Areas
- scripts/medal-bumper.ts[148-187]

### Suggested change
After applying all green `pnpm up` operations (and before committing):
- run `pnpm test && pnpm typecheck && pnpm build`
- if it fails, either (a) fall back to splitting greens, or (b) create the batch PR but mark it as needs-attention with the failure log.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Branch names can collide 🐞 Bug ☼ Reliability
Description
medal-bumper.ts uses only a date stamp in branch names, so reruns on the same day (or retries
after partial failure) can fail to create/switch branches or push because the branch already exists.
Code

scripts/medal-bumper.ts[R148-150]

+if (greens.length > 0) {
+  const branch = `medal-bumper/${MODE}-${todayStamp()}`
+  execSync(`git switch -c ${branch}`, { stdio: 'inherit' })
Evidence
Both the batch and needs-attention branch names are derived from todayStamp() (YYYY-MM-DD) and
thus are not unique per run.

scripts/medal-bumper.ts[148-150]
scripts/medal-bumper.ts[195-196]
scripts/medal-bumper.ts[245-247]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Branch names are not unique, causing collisions on reruns.

### Issue Context
GitHub Actions commonly reruns workflows (manual rerun, retry on infra flake), and scheduled workflows can also be re-triggered.

### Fix Focus Areas
- scripts/medal-bumper.ts[148-150]
- scripts/medal-bumper.ts[195-196]
- scripts/medal-bumper.ts[245-247]

### Suggested change
Include a unique suffix in the branch name, e.g.:
- `GITHUB_RUN_ID` / `GITHUB_RUN_NUMBER` (pass via env from workflow)
- or a full timestamp `new Date().toISOString().replace(/[:.]/g,'-')`

Example: `medal-bumper/${MODE}-${todayStamp()}-${process.env.GITHUB_RUN_ID}`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

6. Channel gating is substring-based 🐞 Bug ≡ Correctness
Description
Adapter steps use contains(inputs.channels, ...) on a comma-separated string, so substring matches
can enable unintended destinations (e.g., channels: slackbot would still run Slack).
Code

.github/workflows/release-notify.yml[R158-178]

+      - name: Adapter — Slack
+        if: steps.payload.outputs.skip != 'true' && contains(inputs.channels, 'slack')
+        working-directory: .medal-social-github/scripts
+        env:
+          SLACK_RELEASES_BOT_TOKEN: ${{ secrets.SLACK_RELEASES_BOT_TOKEN }}
+          SLACK_CHANNEL: ${{ inputs.slack-channel }}
+          PAYLOAD: ${{ steps.payload.outputs.payload }}
+        run: node --experimental-strip-types notify-slack.ts || echo "::warning::Slack post failed (non-fatal)"
+
+      - name: Adapter — Google Chat
+        if: steps.payload.outputs.skip != 'true' && contains(inputs.channels, 'gchat')
+        working-directory: .medal-social-github/scripts
+        env:
+          GCHAT_RELEASES_BOT_TOKEN: ${{ secrets.GCHAT_RELEASES_BOT_TOKEN }}
+          GCHAT_SPACE: ${{ inputs.gchat-space }}
+          PAYLOAD: ${{ steps.payload.outputs.payload }}
+        run: node --experimental-strip-types notify-gchat.ts || echo "::warning::Google Chat post failed (non-fatal)"
+
+      - name: Adapter — Sanity changelog
+        if: steps.payload.outputs.skip != 'true' && contains(inputs.channels, 'sanity')
+        working-directory: .medal-social-github/scripts
Evidence
The workflow’s destination selection relies on contains() against a comma-separated string rather
than exact token membership, which is inherently substring-based.

.github/workflows/release-notify.yml[20-24]
.github/workflows/release-notify.yml[158-184]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Destination gating uses substring matching on a comma-separated list, which can trigger adapters unexpectedly for malformed inputs.

### Issue Context
`inputs.channels` is documented as a comma-separated list like `slack,gchat,sanity`.

### Fix Focus Areas
- .github/workflows/release-notify.yml[158-184]

### Suggested change
Use delimiter guards for exact matches, e.g.:
- Define `CHANNELS: ${{ format(',{0},', inputs.channels) }}` and then
- `if: ... && contains(env.CHANNELS, ',slack,')` (and similarly for `gchat`/`sanity`).

(Or pre-parse via a small step that outputs booleans for each destination.)

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0217e8c48e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/notify-sanity.ts
Comment on lines +91 to +93
const blockContentType = defaultSchema
.get('blockContent')
.fields.find((f: { name: string }) => f.name === 'blockContent').type

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Derive Sanity block type from a document field

defaultSchema.get('blockContent') returns the array type itself, so accessing .fields.find(...) on it will throw at runtime before htmlToBlocks runs. In practice this makes the Sanity adapter fail whenever the Sanity secrets are present, so no changelog document is written. The block content type needs to be resolved from a containing object/document field instead of from the array type directly.

Useful? React with 👍 / 👎.

Comment on lines +94 to +95
prev=$(git rev-parse HEAD~1 2>/dev/null || echo "")
curr=$(git rev-parse HEAD)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Diff against push base SHA, not HEAD~1

The payload builder uses HEAD~1 as the previous revision, which only covers the last commit in the push. For pushes containing multiple commits, the workflow can miss earlier user-facing changes, produce an incomplete compare URL, and even skip notifications if the last commit is docs/CI-only. Using github.event.before as the lower bound is needed to represent the full pushed release range.

Useful? React with 👍 / 👎.

Comment thread scripts/medal-bumper.ts
Comment on lines +149 to +150
const branch = `medal-bumper/${MODE}-${todayStamp()}`
execSync(`git switch -c ${branch}`, { stdio: 'inherit' })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make bumper branch names unique per workflow run

The batch PR branch name is deterministic per mode/day (medal-bumper/<mode>-YYYY-MM-DD), so rerunning the workflow the same day can hit an existing remote branch and fail to push new commits. Because later spawnSync calls do not gate on failure, reruns can silently fail to create/update the expected PR. Add a unique suffix (for example, run ID) or explicitly handle existing branches.

Useful? React with 👍 / 👎.

Comment on lines +87 to +100
# First push to a branch — github.event.before is all-zeroes; bail.
if [[ "${{ github.event.before }}" == "0000000000000000000000000000000000000000" ]]; then
echo 'First push to this branch — no previous commit to diff against; skipping notify.'
echo 'skip=true' >> "$GITHUB_OUTPUT"
exit 0
fi

prev=$(git rev-parse HEAD~1 2>/dev/null || echo "")
curr=$(git rev-parse HEAD)
if [[ -z "$prev" ]]; then
echo 'No previous commit found; skipping notify.'
echo 'skip=true' >> "$GITHUB_OUTPUT"
exit 0
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Wrong release diff base 🐞 Bug ≡ Correctness

release-notify.yml computes prev as HEAD~1 instead of using github.event.before, so
multi-commit pushes/merges to prod will generate payloads/compare links from the wrong base and
can incorrectly skip or misreport releases.
Agent Prompt
### Issue description
`release-notify.yml` diffs against `HEAD~1`, which is not the correct previous branch HEAD when a push contains multiple commits (common for merges). This can produce incorrect `changed` detection, incorrect summaries, and incorrect `compareUrl`.

### Issue Context
For `push` events, GitHub provides the previous branch head as `github.event.before`. That value should be used as the diff base.

### Fix Focus Areas
- .github/workflows/release-notify.yml[87-143]

### Suggested change
- Set `prev="${{ github.event.before }}"` and `curr="${{ github.sha }}"` (or `curr=$(git rev-parse HEAD)`), and ensure the commits are available (you already use `fetch-depth: 0`).
- Keep the all-zeroes guard for first-push and consider forced-push behavior explicitly if desired.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread scripts/medal-bumper.ts
Comment on lines +152 to +203
for (const g of greens) {
spawnSync('pnpm', ['up', `${g.name}@${g.to}`], { stdio: 'inherit' })
}
spawnSync('git', ['add', 'package.json', 'pnpm-lock.yaml'], { stdio: 'inherit' })

const commitMsg = `chore(deps): ${MODE} bump batch (${greens.length} packages)`
spawnSync('git', ['commit', '-m', commitMsg], { stdio: 'inherit' })
spawnSync('git', ['push', '-u', 'origin', branch], { stdio: 'inherit' })

const body = await writePrBodyForBatch(greens, MODE, REPO)
const bodyFile = path.join(mkdtempSync(path.join(tmpdir(), 'bumper-')), 'body.md')
writeFileSync(bodyFile, body, 'utf8')

spawnSync(
'gh',
[
'pr',
'create',
'--repo',
REPO,
'--base',
BASE_BRANCH,
'--head',
branch,
'--title',
commitMsg,
'--body-file',
bodyFile,
'--label',
'dependencies',
'--label',
'medal-bumper',
],
{ stdio: 'inherit' },
)
}

// ── 6. Isolate red bumps into individual PRs ───────────────────────────────

for (const r of reds) {
console.log(`\n[bumper] Opening isolated PR for failing bump ${r.name}…`)
execSync(`git reset --hard ${baselineSha}`, { stdio: 'inherit' })

const branch = `medal-bumper/needs-attention-${r.name.replace(/[@\/]/g, '-')}-${todayStamp()}`
execSync(`git switch -c ${branch}`, { stdio: 'inherit' })

// Bump (don't run tests — the PR exists to surface the failure to humans).
spawnSync('pnpm', ['up', `${r.name}@${r.to}`], { stdio: 'inherit' })
spawnSync('git', ['add', 'package.json', 'pnpm-lock.yaml'], { stdio: 'inherit' })
spawnSync('git', ['commit', '-m', `chore(deps): bump ${r.name} to ${r.to} [needs attention]`], { stdio: 'inherit' })
spawnSync('git', ['push', '-u', 'origin', branch], { stdio: 'inherit' })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Command failures ignored 🐞 Bug ☼ Reliability

medal-bumper.ts uses spawnSync for critical commands (pnpm up, git commit, git push, `gh
pr create`) but does not check exit codes, so it can print "Done" while failing to push branches or
create PRs (especially for red bumps where pnpm up may fail again).
Agent Prompt
### Issue description
Critical steps can fail silently because `spawnSync` return values aren’t checked. This can lead to skipped PRs, unpushed branches, or failed commits with no hard failure.

### Issue Context
This is especially problematic in the red-bump PR path: the script attempts `pnpm up` again and proceeds to `git commit/push` even if the bump didn’t apply.

### Fix Focus Areas
- scripts/medal-bumper.ts[152-187]
- scripts/medal-bumper.ts[191-231]

### Suggested change
- Wrap `spawnSync` in a helper like `runOrThrow(cmd, args, opts)` that throws on `status !== 0` or `error`.
- For red bumps, if the bump command fails, skip PR creation (or create a PR/issue that only contains logs) rather than attempting to commit empty/no-op changes.
- Consider switching the critical `spawnSync` calls to `execSync` when you want hard-fail semantics.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

…config

- notify-sanity: bump apiVersion 2024-10-01 → 2025-12-23 to match
  NextMedal/src/sanity/lib/project.ts (where the schema is authored).
- notify-gchat: use the real @chat-adapter/gchat config shape
  (`credentials: <SA JSON>`). The previous `serviceAccount` /
  `serviceAccountToken` keys do not exist in GoogleChatAdapterConfig and
  the adapter has no raw-token mode — fail fast with a clear error if
  the secret isn't valid JSON.
- README: clarify GCHAT_RELEASES_BOT_TOKEN must be the full SA JSON blob.
@alioftech

Copy link
Copy Markdown
Contributor Author

Addressed open questions from the implementer (commit 72792b4):

1. Sanity apiVersion — bumped 2024-10-012025-12-23 in notify-sanity.ts to match NextMedal/src/sanity/lib/project.ts (the schema source of truth). NextMedal pins via NEXT_PUBLIC_SANITY_API_VERSION || '2025-12-23'.

3. @chat-adapter/gchat token format — verified the actual config surface by inspecting node_modules/@chat-adapter/gchat/dist/index.d.ts. The GoogleChatAdapterConfig union accepts credentials (SA JSON), auth (custom client), useApplicationDefaultCredentials, or env-driven auto-detect — there is no serviceAccount / serviceAccountToken key, and no raw-bearer-token mode. Reworked notify-gchat.ts to:

  • parse GCHAT_RELEASES_BOT_TOKEN as JSON and pass it as credentials
  • fail fast with a clear message if it isn't valid JSON (instead of silently mis-configuring with a fake key)

Updated the secrets table in README.md to make the JSON-only requirement explicit.

(auto-cs uses Slack only — no gchat reference implementation in customers/, so the API contract was verified directly from the published adapter.)

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 72792b4197

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +113 to +114
summary=$(git diff "$prev" "$curr" -- '**/CHANGELOG.md' \
| grep -E '^\+' | grep -vE '^\+\+\+' | sed 's/^\+//' | head -40)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Match root CHANGELOG when extracting release summary

The CHANGELOG extraction uses git diff ... -- '**/CHANGELOG.md' while the step is running with set -euo pipefail. That pathspec does not match a top-level CHANGELOG.md, so in repos where the changelog lives at the root the pipeline on the next line receives no + lines and grep exits non-zero, which aborts payload generation and fails the notify job instead of sending notifications.

Useful? React with 👍 / 👎.

Comment thread scripts/notify-sanity.ts
import { createClient } from '@sanity/client'
import { JSDOM } from 'jsdom'
import { htmlToBlocks } from '@portabletext/block-tools'
import { Schema } from '@sanity/schema'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Declare @sanity/schema as a direct script dependency

notify-sanity.ts imports Schema from @sanity/schema, but scripts/package.json does not declare that package. On a clean runner this can fail with ERR_MODULE_NOT_FOUND before the adapter runs, which means Sanity changelog writes are skipped behind the non-fatal wrapper even when Sanity is enabled.

Useful? React with 👍 / 👎.

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.

1 participant