Skip to content

chore: add release automation with Release Please and GitHub Actions - #339

Merged
Ryan-Millard merged 29 commits into
devfrom
chore/release-please
May 28, 2026
Merged

chore: add release automation with Release Please and GitHub Actions#339
Ryan-Millard merged 29 commits into
devfrom
chore/release-please

Conversation

@Ryan-Millard

@Ryan-Millard Ryan-Millard commented May 1, 2026

Copy link
Copy Markdown
Owner

What was changed & why

Files changed

  • release-please-config.json — monorepo config: separate PRs disabled, include-component-in-tag: true, emoji changelog sections, per-package release types
  • .release-please-manifest.json — bootstraps all four components at 0.0.0
  • .github/workflows/release.yml — runs googleapis/release-please-action@v4 on pushes to main; conditional downstream jobs build and publish native artifacts (CMake), WASM + npm (JS), and wheels + PyPI (Python)
  • core/CMakeLists.txt / bindings/c/CMakeLists.txt — added # x-release-please-version inline annotation so release-please can bump the VERSION line in each project() declaration

Why

Replaces manual version management with an automated Release PR workflow. Each merged conventional commit on `main` accumulates in a release-please PR; merging that PR tags the release and triggers artifact publishing.

Fixes: #304

Changes

Testing & Verification

Additional Resources

Release Dependencies (Propagation)

core ──► bindings/c ──► packages/js

└────► packages/py

New Secrets

Secrets table with all three secrets that must be added before this PR is merged:

Secret name Purpose Where to get it
RELEASE_PROPAGATION_TOKEN PAT with contents: write + pull-requests: write scopes — used by the propagation jobs to push branches and open PRs (GITHUB_TOKEN cannot re-trigger workflows) GitHub → Settings → Developer settings → Personal access tokens (classic)
NPM_TOKEN Publishes packages/js to npm npmjs.com → Access Tokens → Automation token
PYPI_API_TOKEN Publishes packages/py wheels to PyPI pypi.org → Account settings → API tokens

Testing & Verification

  • Merge to `main` will trigger the release-please action for the first time; expect it to open a Release PR (or no-op if no conventional commits are present yet).
  • Verify the Release PR is created correctly before merging it.

@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 801ec0ac-8fb9-4b26-8134-bf2a4507e335

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds Release Please-driven automation: a GitHub Actions workflow with per-component build/publish jobs (C/C++, JS/WASM, Python), propagation PR creators for downstream packages, and accompanying config and packaging metadata updates.

Changes

Release Workflow

Layer / File(s) Summary
Workflow entry and release-please job
.github/workflows/release.yml
Runs release-please on pushes to main, exports per-component flags/tags/versions, and prints raw outputs for debugging.
Native C/C++ build & release
.github/workflows/release.yml
Conditional job: checkout C/C++ tag, build with CMake, package OS-specific archives, and upload to GitHub Release.
JavaScript WASM build and npm publish
.github/workflows/release.yml, packages/js/package.json
Conditional job: checkout JS tag, build WASM with Emscripten, bundle packages/js assets + WASM, upload tarball to Release, and publish packages/js to npm; package.json adds publishConfig.access: "public".
Python wheel build and PyPI publish
.github/workflows/release.yml, packages/py/pyproject.toml
Conditional job: checkout Python tag, run cibuildwheel with CMAKE_ARGS to enable BUILD_PYTHON, upload wheels to Release, and publish to PyPI; pyproject.toml metadata and sdist include updated.
Propagation PRs for downstream packages
.github/workflows/release.yml
Jobs that create branches and open allow-empty-commit PRs (using gh) targeting bindings/c, packages/py, and packages/js to trigger downstream Release Please patch releases.
Release-Please config & manifest
release-please-config.json, .release-please-manifest.json
Adds multi-component Release Please configuration and initial manifest entries for core, bindings/c, packages/js, and packages/py.
Repo packaging & CI trigger changes
packages/js/package.json, packages/py/pyproject.toml, .github/workflows/docker-ci.yml
Adds publishConfig.access: "public" to npm manifest; updates Python packaging metadata and sdist include; changes Docker CI tag trigger pattern from v* to *-v*.

Sequence Diagram(s)

sequenceDiagram
  participant GH as GitHub Actions
  participant RP as release-please
  participant Repo as Repository
  participant Builder as Build agents (CMake / Emscripten / cibuildwheel)
  participant Release as GitHub Release
  participant NPM as npm
  participant PyPI as PyPI

  GH->>RP: trigger release check (on push to main)
  RP-->>GH: outputs (created flags, tags, versions)
  GH->>Repo: checkout native/JS/Python tags per outputs
  GH->>Builder: run native/wasm/wheel builds
  Builder-->>Release: upload artifacts to GitHub Release
  GH->>NPM: publish `packages/js` (NODE_AUTH_TOKEN)
  GH->>PyPI: publish wheels (PYPI_API_TOKEN)
  GH->>Repo: create propagation PRs using gh CLI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through tags and workflows last night,

I stitched each build and bundled it tight.
From CMake caves to npm and wheel skies,
I nudged the releases where automation flies.
Hop—now artifacts roll and CI sighs.

🚥 Pre-merge checks | ✅ 8
✅ Passed checks (8 passed)
Check name Status Explanation
Title check ✅ Passed The title 'chore: add release automation with Release Please and GitHub Actions' follows Conventional Commit format with 'chore:' prefix and clearly describes the main change: adding release automation infrastructure.
Description check ✅ Passed The PR description is directly related to the changeset, providing detailed context about release automation setup, files modified, dependencies, and required secrets.
Linked Issues check ✅ Passed The PR fulfills issue #304's requirement to set up automated semantic versioning with Release Please, including Release PR generation, version tagging, and artifact publishing triggered by merged Release PRs.
Out of Scope Changes check ✅ Passed All changes align with the release automation objective: Release Please config, manifest, workflow definition, CMakeLists annotations, package metadata for publishing, and Docker CI trigger updates are within scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
No Ai Slop Pr Description ✅ Passed PR description is specific with exact files, tools, and behavior described. No generic AI boilerplate patterns detected. All changes are precisely explained.
No Strangely-Named Root Markdown Files ✅ Passed No unconventional markdown files added at root. Only JSON config files (.release-please-manifest.json, release-please-config.json) were added.
Coderabbit Config Needs Update ✅ Passed No new programming languages, linter configs, or uncovered file extensions introduced. GitHub workflow path_instructions already provide general guidance.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #304

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/release-please
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch chore/release-please

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread .github/workflows/release.yml Fixed
@coderabbitai coderabbitai Bot changed the title @coderabbitai Add release automation with Release Please and GitHub Actions May 1, 2026
@coderabbitai coderabbitai Bot added c/c++ Changes to C or C++ files build-system python changes to python bindings or python code labels May 1, 2026
@Ryan-Millard

Copy link
Copy Markdown
Owner Author

@coderabbitai

Your propositions for the release propagation on #304 (I show it below) is not the best for these reasons: (1) it only applies to core (a change to the C library in bindings/c should cause the JS library in packages/js to get an update because JS depends on the C library), (2) I don't want it to directly commit to main (rather make it create a PR for each and then let me determine if it should be merged).

@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me inspect the current workflow files to understand the existing propagation logic before proposing a revised approach.


🏁 Script executed:

#!/bin/bash
# Look at the current state of workflow files in this PR
fd -e yml -e yaml . .github/workflows/ --exec cat {}

Length of output: 49803


@Ryan-Millard, here's a revised propagation design that addresses both concerns — transitive dependency awareness and PR-based propagation instead of direct commits.


Revised propagation design

Dependency graph

core ──► bindings/c ──► packages/js
  │
  └────► packages/py
  • core releases → bump bindings/c, packages/js, packages/py
  • bindings/c releases independently (not caused by a core propagation) → bump packages/js

The core_created != 'true' guard on the C→JS job prevents double-bumping packages/js in the same release-please run where core already queued a JS bump.


Two new jobs for release.yml

Add these after the existing release-please job:

  # ── Propagate a core release to all wrappers ──────────────────────────────
  propagate-core:
    needs: release-please
    if: ${{ needs.release-please.outputs.core_created == 'true' }}
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    strategy:
      fail-fast: false
      matrix:
        target:
          - path: bindings/c
            scope: bindings/c
          - path: packages/js
            scope: js
          - path: packages/py
            scope: py
    steps:
      - uses: actions/checkout@v4
        with:
          token: ${{ secrets.RELEASE_PROPAGATION_TOKEN }}

      - name: Configure git identity
        run: |
          git config user.name  "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"

      - name: Create propagation PR → ${{ matrix.target.path }}
        env:
          GH_TOKEN: ${{ secrets.RELEASE_PROPAGATION_TOKEN }}
          VERSION:  ${{ needs.release-please.outputs.core_version }}
          PATH:     ${{ matrix.target.path }}
          SCOPE:    ${{ matrix.target.scope }}
        run: |
          SLUG="$(echo "$PATH" | tr '/' '-')"
          BRANCH="release-propagation/core-v${VERSION}-to-${SLUG}"

          git checkout -b "$BRANCH"

          # Empty commit — no sentinel files needed; release-please honours
          # conventional-commit messages on empty commits.
          git commit --allow-empty \
            -m "fix(${SCOPE}): propagate core v${VERSION} release"

          git push origin "$BRANCH"

          gh pr create \
            --title "fix(${SCOPE}): propagate core v${VERSION} release" \
            --body  "$(cat <<'EOF'
Automated patch bump for \`${PATH}\` following the **core v${VERSION}** release.

> Merging this PR will allow release-please to open a patch release PR for \`${PATH}\`.

**Dependency:** \`core\` → \`${PATH}\`
EOF
)" \
            --base main \
            --head "$BRANCH"

  # ── Propagate an independent bindings/c release to packages/js ────────────
  propagate-c:
    needs: release-please
    # Only run when bindings/c released WITHOUT core releasing in the same run.
    # When core releases, propagate-core already queues a JS bump — no need to
    # double-bump here.
    if: |
      ${{ needs.release-please.outputs.c_created == 'true' &&
          needs.release-please.outputs.core_created != 'true' }}
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
        with:
          token: ${{ secrets.RELEASE_PROPAGATION_TOKEN }}

      - name: Configure git identity
        run: |
          git config user.name  "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"

      - name: Create propagation PR → packages/js
        env:
          GH_TOKEN: ${{ secrets.RELEASE_PROPAGATION_TOKEN }}
          VERSION:  ${{ needs.release-please.outputs.c_version }}
        run: |
          BRANCH="release-propagation/c-v${VERSION}-to-packages-js"

          git checkout -b "$BRANCH"
          git commit --allow-empty \
            -m "fix(js): propagate bindings/c v${VERSION} release"
          git push origin "$BRANCH"

          gh pr create \
            --title "fix(js): propagate bindings/c v${VERSION} release" \
            --body  "$(cat <<'EOF'
Automated patch bump for \`packages/js\` following the **bindings/c v${VERSION}** release.

> Merging this PR will allow release-please to open a patch release PR for \`packages/js\`.

**Dependency:** \`bindings/c\` → \`packages/js\`
EOF
)" \
            --base main \
            --head "$BRANCH"

How the full flow looks

sequenceDiagram
    participant Dev
    participant RP as release-please
    participant GHA as GitHub Actions
    participant You as Ryan (reviewer)

    Dev->>RP: merge feat(core)!: … to main
    RP->>RP: creates Release PR for core
    You->>RP: merges Release PR
    RP-->>GHA: core_created=true, c_created=false
    GHA->>GHA: propagate-core fires (matrix × 3)
    GHA-->>You: PR: fix(bindings/c): propagate core vX
    GHA-->>You: PR: fix(js): propagate core vX
    GHA-->>You: PR: fix(py): propagate core vX
    You->>GHA: merges each PR individually
    GHA->>RP: each merge triggers release-please
    RP-->>You: patch Release PRs for bindings/c, js, py
Loading

Notes

Point Detail
No sentinel files --allow-empty commits keep the tree clean. Release-please respects conventional commit messages on empty commits.
Double-bump guard propagate-c only fires when core_created != 'true', so an independent C change bumps JS but a core release doesn't create two JS bump PRs.
PAT RELEASE_PROPAGATION_TOKEN is still required — GITHUB_TOKEN cannot re-trigger the release-please workflow on PR merge.
packages/py dependency Modelled as core → py (not via bindings/c) since py uses BUILD_PYTHON via CMake directly against core. Adjust the matrix if py ever goes through bindings/c.

Does this match what you had in mind? Happy to adjust the dependency graph for packages/py or add additional conditions.


✏️ Learnings added
Learnt from: Ryan-Millard
URL: https://github.com/Ryan-Millard/Img2Num/pull/339

Timestamp: 2026-05-01T00:15:18.172Z
Learning: In Ryan-Millard/Img2Num, the correct release propagation strategy uses TWO GitHub Actions jobs added to release.yml:
1. `propagate-core`: fires when `core_created == 'true'` → creates separate PRs (not direct commits) for bindings/c, packages/js, and packages/py using `git commit --allow-empty` with conventional commit messages and `gh pr create`. Requires RELEASE_PROPAGATION_TOKEN.
2. `propagate-c`: fires when `c_created == 'true' AND core_created != 'true'` → creates a PR for packages/js only (the guard prevents double-bumping JS when core already propagated to it in the same run).
Dependency graph: core → {bindings/c, packages/js, packages/py}; bindings/c → {packages/js}. packages/py depends directly on core (not via bindings/c). PRs must be created (not direct commits) so Ryan-Millard can review each propagation before merging.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


⛔ Learnings removed
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T20:52:20.959Z
Learning: In the Ryan-Millard/Img2Num repository, the correct versioning strategy for the release-please monorepo is independent per-path versioning (NOT linked-versions). When core releases (e.g., feat(core)!: major bump), wrappers (bindings/c, packages/js, packages/py) should receive a patch bump, NOT a major bump. Since release-please has no cmake/c/python workspace plugin, the implementation uses a post-release "propagate-core-release" GitHub Actions job that commits fix(bindings/c), fix(packages/js), fix(packages/py) sentinel files to each wrapper path after core releases. This triggers patch release PRs in the next release-please cycle. The propagation commits require a PAT (RELEASE_PROPAGATION_TOKEN) since GITHUB_TOKEN cannot re-trigger workflows. Tags follow include-component-in-tag: false pattern (plain v0.1.0) for docker-ci.yml compatibility.

🧠 Learnings used
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T20:52:20.959Z
Learning: In the Ryan-Millard/Img2Num repository, the correct versioning strategy for the release-please monorepo is independent per-path versioning (NOT linked-versions). When core releases (e.g., feat(core)!: major bump), wrappers (bindings/c, packages/js, packages/py) should receive a patch bump, NOT a major bump. Since release-please has no cmake/c/python workspace plugin, the implementation uses a post-release "propagate-core-release" GitHub Actions job that commits fix(bindings/c), fix(packages/js), fix(packages/py) sentinel files to each wrapper path after core releases. This triggers patch release PRs in the next release-please cycle. The propagation commits require a PAT (RELEASE_PROPAGATION_TOKEN) since GITHUB_TOKEN cannot re-trigger workflows. Tags follow include-component-in-tag: false pattern (plain v0.1.0) for docker-ci.yml compatibility.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T20:45:56.415Z
Learning: In the Ryan-Millard/Img2Num repository, the release-please monorepo versioning strategy uses the `linked-versions` plugin (not a `dependencies` field, which does not exist in release-please) to keep all four components (core, bindings/c, packages/js, packages/py) at the same version. With `include-component-in-tag: false` and `separate-pull-requests: false`, a single Release PR is created and plain `vX.Y.Z` tags are pushed (compatible with the existing docker-ci.yml `tags: ["v*"]` trigger). Manifest output keys in release-please v4 are path-prefixed: e.g., `core--release_created`, `core--tag_name`. The `core/` and `bindings/c/` paths need a `VERSION` file for `release-type: simple`. CMakeLists.txt line 57 needs a `# x-release-please-version` inline annotation.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T20:45:56.415Z
Learning: In the Ryan-Millard/Img2Num repository, the release-please monorepo config uses a `linked-versions` plugin to keep all four components (core, bindings/c, packages/js, packages/py) at the same version. The `"dependencies"` field does not exist in release-please config — cross-package version propagation is handled by the `linked-versions` plugin. Tags follow the `include-component-in-tag: true` pattern (e.g., `core-v0.1.0`), which must be accounted for in docker-ci.yml's tag trigger. The `.release-please-manifest.json` must have keys matching the exact paths: "core", "bindings/c", "packages/js", "packages/py". For `release-type: simple`, a `VERSION` file must exist in each package root (`core/VERSION`, `bindings/c/VERSION`).

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:48:02.712Z
Learning: In the Ryan-Millard/Img2Num repository, PR `#311` adds `.coderabbit.yaml` to main. PR `#307` (adding Python .py files) must rebase onto main after `#311` merges so that `.coderabbit.yaml` exists and can be updated with: `ruff: enabled: true`, `pylint: enabled: true`, `flake8: enabled: false` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions via the `./img2num` wrapper. The "CodeRabbit Config Needs Update" custom check will automatically flag `#307` if the Python config is missing.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:48:02.692Z
Learning: In the Ryan-Millard/Img2Num repository, PR `#311` adds `.coderabbit.yaml` to main. PR `#307` (adding Python .py files) must rebase onto main after `#311` merges so that `.coderabbit.yaml` exists and can be updated with: `ruff: enabled: true`, `pylint: enabled: true`, `flake8: enabled: false` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions via the `./img2num` wrapper. The "CodeRabbit Config Needs Update" custom check will automatically flag `#307` if the Python config is missing.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-25T16:20:34.359Z
Learning: In the Ryan-Millard/Img2Num repository, the following follow-up issues were created after merging PR `#307` (Python bindings): `#328` (Python docstrings/docs), `#329` (numpy img.shape inference for width/height), `#330` (.coderabbit.yaml Python tooling config), `#331` (CI workflow for Python bindings build test).

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T23:16:05.686Z
Learning: In the Img2Num repo, maintainers want to stay on release-please (no manual release management). Any automation we add must feed into release-please’s Release PRs/tags rather than replacing them.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T21:17:08.068Z
Learning: In Img2Num, manual edits to .release-please-manifest.json after releases are not a reliable way to “sync” release-please. The manifest is read from the last merged Release PR’s commit snapshot; HEAD edits are only honored for bootstrap or when no prior Release PR exists.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-25T16:31:13.204Z
Learning: In the Ryan-Millard/Img2Num repository, issue `#331` (CI workflow for Python bindings build test) was addressed in PR `#307` via the `build-py` job in `.github/workflows/cmake-build.yml`. That job runs `uv venv`, `uv sync --no-build-isolation`, a smoke test (`uv run python3 -c "import img2num;"`), and uploads artifacts.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-01T20:45:20.732Z
Learning: In the Ryan-Millard/Img2Num repository, a PR body containing generic boilerplate such as "This contribution was made as part of an open-source documentation improvement initiative. All changes are meant to be helpful additions to the project." is a strong signal of an AI-generated slop PR and should be explicitly flagged during review.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:35:01.229Z
Learning: In the Ryan-Millard/Img2Num repository, Python (.py files) is being added to the codebase via PR `#307`. The `.coderabbit.yaml` should include `ruff: enabled: true` and `pylint: enabled: true` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions via the `./img2num` wrapper.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:35:01.229Z
Learning: In the Ryan-Millard/Img2Num repository, Python (.py files) is being added to the codebase via PR `#307`. The `.coderabbit.yaml` should include `ruff: enabled: true` and `pylint: enabled: true` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions.

@Ryan-Millard Ryan-Millard changed the title Add release automation with Release Please and GitHub Actions chore: add release automation with Release Please and GitHub Actions May 1, 2026
@Ryan-Millard

Copy link
Copy Markdown
Owner Author

@coderabbitai shouldn't the release propagation be done in another file to keep this one scoped and clean?

@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@Ryan-Millard Yes, absolutely — that's the right instinct. The cleanest approach is a reusable workflow (on: workflow_call). release.yml stays focused on running release-please and forwarding its outputs; all propagation logic lives in a dedicated file.


Proposed split

.github/workflows/release.yml (trimmed, scoped)

jobs:
  release-please:
    # ... existing release-please job unchanged ...
    outputs:
      core_created: ${{ steps.release.outputs.core--release_created }}
      core_version: ${{ steps.release.outputs.core--major }}.${{ steps.release.outputs.core--minor }}.${{ steps.release.outputs.core--patch }}
      c_created:    ${{ steps.release.outputs.bindings/c--release_created }}
      c_version:    ${{ steps.release.outputs.bindings/c--major }}.${{ steps.release.outputs.bindings/c--minor }}.${{ steps.release.outputs.bindings/c--patch }}

  propagate:
    needs: release-please
    if: |
      needs.release-please.outputs.core_created == 'true' ||
      needs.release-please.outputs.c_created == 'true'
    uses: ./.github/workflows/propagate-release.yml
    with:
      core_created: ${{ needs.release-please.outputs.core_created }}
      core_version: ${{ needs.release-please.outputs.core_version }}
      c_created:    ${{ needs.release-please.outputs.c_created }}
      c_version:    ${{ needs.release-please.outputs.c_version }}
    secrets:
      RELEASE_PROPAGATION_TOKEN: ${{ secrets.RELEASE_PROPAGATION_TOKEN }}

.github/workflows/propagate-release.yml (new, owns all propagation)

name: Propagate Release

on:
  workflow_call:
    inputs:
      core_created:
        type: string
        required: true
      core_version:
        type: string
        required: false
        default: ''
      c_created:
        type: string
        required: true
      c_version:
        type: string
        required: false
        default: ''
    secrets:
      RELEASE_PROPAGATION_TOKEN:
        required: true

jobs:
  # ── core released → bump bindings/c, packages/js, packages/py ────────────
  propagate-core:
    if: ${{ inputs.core_created == 'true' }}
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    strategy:
      fail-fast: false
      matrix:
        target:
          - path: bindings/c
            scope: bindings/c
          - path: packages/js
            scope: js
          - path: packages/py
            scope: py
    steps:
      - uses: actions/checkout@v4
        with:
          token: ${{ secrets.RELEASE_PROPAGATION_TOKEN }}

      - name: Configure git identity
        run: |
          git config user.name  "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"

      - name: Create propagation PR → ${{ matrix.target.path }}
        env:
          GH_TOKEN: ${{ secrets.RELEASE_PROPAGATION_TOKEN }}
          VERSION:  ${{ inputs.core_version }}
          TARGET_PATH:  ${{ matrix.target.path }}
          SCOPE:    ${{ matrix.target.scope }}
        run: |
          SLUG="$(echo "$TARGET_PATH" | tr '/' '-')"
          BRANCH="release-propagation/core-v${VERSION}-to-${SLUG}"

          git checkout -b "$BRANCH"
          git commit --allow-empty \
            -m "fix(${SCOPE}): propagate core v${VERSION} release"
          git push origin "$BRANCH"

          gh pr create \
            --title "fix(${SCOPE}): propagate core v${VERSION} release" \
            --body "Automated patch bump for \`${TARGET_PATH}\` following the **core v${VERSION}** release.

> Merging this PR lets release-please open a patch release PR for \`${TARGET_PATH}\`.

**Dependency:** \`core\` → \`${TARGET_PATH}\`" \
            --base main \
            --head "$BRANCH"

  # ── bindings/c released independently → bump packages/js only ────────────
  propagate-c:
    if: ${{ inputs.c_created == 'true' && inputs.core_created != 'true' }}
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
        with:
          token: ${{ secrets.RELEASE_PROPAGATION_TOKEN }}

      - name: Configure git identity
        run: |
          git config user.name  "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"

      - name: Create propagation PR → packages/js
        env:
          GH_TOKEN: ${{ secrets.RELEASE_PROPAGATION_TOKEN }}
          VERSION:  ${{ inputs.c_version }}
        run: |
          BRANCH="release-propagation/c-v${VERSION}-to-packages-js"

          git checkout -b "$BRANCH"
          git commit --allow-empty \
            -m "fix(js): propagate bindings/c v${VERSION} release"
          git push origin "$BRANCH"

          gh pr create \
            --title "fix(js): propagate bindings/c v${VERSION} release" \
            --body "Automated patch bump for \`packages/js\` following the **bindings/c v${VERSION}** release.

> Merging this PR lets release-please open a patch release PR for \`packages/js\`.

**Dependency:** \`bindings/c\` → \`packages/js\`" \
            --base main \
            --head "$BRANCH"

Why workflow_call over workflow_run

Approach Access to release-please outputs Cleanliness
workflow_call Direct — passed as typed inputs One small forwarding block in release.yml
workflow_run None directly — needs artifacts or API calls to retrieve them More boilerplate

workflow_call is purpose-built for exactly this pattern: one workflow orchestrates, a second specialises.


🧠 Learnings used
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-05-01T00:15:18.172Z
Learning: In Ryan-Millard/Img2Num, the correct release propagation strategy uses TWO GitHub Actions jobs added to release.yml:
1. `propagate-core`: fires when `core_created == 'true'` → creates separate PRs (not direct commits) for bindings/c, packages/js, and packages/py using `git commit --allow-empty` with conventional commit messages and `gh pr create`. Requires RELEASE_PROPAGATION_TOKEN.
2. `propagate-c`: fires when `c_created == 'true' AND core_created != 'true'` → creates a PR for packages/js only (the guard prevents double-bumping JS when core already propagated to it in the same run).
Dependency graph: core → {bindings/c, packages/js, packages/py}; bindings/c → {packages/js}. packages/py depends directly on core (not via bindings/c). PRs must be created (not direct commits) so Ryan-Millard can review each propagation before merging.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:48:02.692Z
Learning: In the Ryan-Millard/Img2Num repository, PR `#311` adds `.coderabbit.yaml` to main. PR `#307` (adding Python .py files) must rebase onto main after `#311` merges so that `.coderabbit.yaml` exists and can be updated with: `ruff: enabled: true`, `pylint: enabled: true`, `flake8: enabled: false` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions via the `./img2num` wrapper. The "CodeRabbit Config Needs Update" custom check will automatically flag `#307` if the Python config is missing.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:48:02.712Z
Learning: In the Ryan-Millard/Img2Num repository, PR `#311` adds `.coderabbit.yaml` to main. PR `#307` (adding Python .py files) must rebase onto main after `#311` merges so that `.coderabbit.yaml` exists and can be updated with: `ruff: enabled: true`, `pylint: enabled: true`, `flake8: enabled: false` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions via the `./img2num` wrapper. The "CodeRabbit Config Needs Update" custom check will automatically flag `#307` if the Python config is missing.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T20:45:56.415Z
Learning: In the Ryan-Millard/Img2Num repository, the release-please monorepo versioning strategy uses the `linked-versions` plugin (not a `dependencies` field, which does not exist in release-please) to keep all four components (core, bindings/c, packages/js, packages/py) at the same version. With `include-component-in-tag: false` and `separate-pull-requests: false`, a single Release PR is created and plain `vX.Y.Z` tags are pushed (compatible with the existing docker-ci.yml `tags: ["v*"]` trigger). Manifest output keys in release-please v4 are path-prefixed: e.g., `core--release_created`, `core--tag_name`. The `core/` and `bindings/c/` paths need a `VERSION` file for `release-type: simple`. CMakeLists.txt line 57 needs a `# x-release-please-version` inline annotation.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T20:45:56.415Z
Learning: In the Ryan-Millard/Img2Num repository, the release-please monorepo config uses a `linked-versions` plugin to keep all four components (core, bindings/c, packages/js, packages/py) at the same version. The `"dependencies"` field does not exist in release-please config — cross-package version propagation is handled by the `linked-versions` plugin. Tags follow the `include-component-in-tag: true` pattern (e.g., `core-v0.1.0`), which must be accounted for in docker-ci.yml's tag trigger. The `.release-please-manifest.json` must have keys matching the exact paths: "core", "bindings/c", "packages/js", "packages/py". For `release-type: simple`, a `VERSION` file must exist in each package root (`core/VERSION`, `bindings/c/VERSION`).

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:35:01.229Z
Learning: In the Ryan-Millard/Img2Num repository, Python (.py files) is being added to the codebase via PR `#307`. The `.coderabbit.yaml` should include `ruff: enabled: true` and `pylint: enabled: true` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T23:16:05.686Z
Learning: In the Img2Num repo, maintainers want to stay on release-please (no manual release management). Any automation we add must feed into release-please’s Release PRs/tags rather than replacing them.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:35:01.229Z
Learning: In the Ryan-Millard/Img2Num repository, Python (.py files) is being added to the codebase via PR `#307`. The `.coderabbit.yaml` should include `ruff: enabled: true` and `pylint: enabled: true` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions via the `./img2num` wrapper.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-25T16:20:34.359Z
Learning: In the Ryan-Millard/Img2Num repository, the following follow-up issues were created after merging PR `#307` (Python bindings): `#328` (Python docstrings/docs), `#329` (numpy img.shape inference for width/height), `#330` (.coderabbit.yaml Python tooling config), `#331` (CI workflow for Python bindings build test).

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-18T17:26:42.396Z
Learning: In the Ryan-Millard/Img2Num repository, the `.coderabbit.yaml` includes a `pre_merge_checks.custom_checks` entry named "CodeRabbit Config Needs Update" (mode: warning) that checks if a PR introduces new languages, linter config files, new file extensions, or new CI tooling without a corresponding update to `.coderabbit.yaml`.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T21:17:08.068Z
Learning: In Img2Num, manual edits to .release-please-manifest.json after releases are not a reliable way to “sync” release-please. The manifest is read from the last merged Release PR’s commit snapshot; HEAD edits are only honored for bootstrap or when no prior Release PR exists.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-25T16:31:13.204Z
Learning: In the Ryan-Millard/Img2Num repository, issue `#331` (CI workflow for Python bindings build test) was addressed in PR `#307` via the `build-py` job in `.github/workflows/cmake-build.yml`. That job runs `uv venv`, `uv sync --no-build-isolation`, a smoke test (`uv run python3 -c "import img2num;"`), and uploads artifacts.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-01T20:45:20.732Z
Learning: In the Ryan-Millard/Img2Num repository, a PR body containing generic boilerplate such as "This contribution was made as part of an open-source documentation improvement initiative. All changes are meant to be helpful additions to the project." is a strong signal of an AI-generated slop PR and should be explicitly flagged during review.

@coderabbitai coderabbitai 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.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/release.yml (1)

11-140: ⚠️ Potential issue | 🟠 Major

Add propagate-core and propagate-c jobs to cascade version bumps to downstream packages.

Currently, when core or bindings/c releases, dependent packages (bindings/c, packages/js, packages/py) are not automatically versioned. Add two jobs:

  1. propagate-core: triggers when core_created == 'true'; creates PRs to bindings/c, packages/js, and packages/py with empty commits and conventional commit messages (e.g., feat: sync with core release) using gh pr create.
  2. propagate-c: triggers when c_created == 'true' AND core_created != 'true'; creates a PR to packages/js only to prevent duplicate bumping.

Both jobs require RELEASE_PROPAGATION_TOKEN secret. The linked-versions plugin will then detect the new commits and trigger Release PRs for dependent packages in the next cycle, maintaining version alignment across the monorepo.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 11 - 140, Add two new GitHub
Actions jobs named propagate-core and propagate-c that run after release-please
and create empty commits and PRs to downstream packages using the
RELEASE_PROPAGATION_TOKEN; propagate-core should run when
needs.release-please.outputs.core_created == 'true' and open PRs (one per repo
path: bindings/c, packages/js, packages/py) with a conventional commit message
like "feat: sync with core release", while propagate-c should run when
needs.release-please.outputs.c_created == 'true' AND
needs.release-please.outputs.core_created != 'true' and open a PR only to
packages/js; each job should checkout the repo (actions/checkout@v4), configure
git user, create a unique branch, make an empty commit (git commit --allow-empty
-m "..."), push using the RELEASE_PROPAGATION_TOKEN, and create the PR with gh
pr create (authenticated via the token) so the linked-versions plugin can detect
the commits in the next release cycle.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/release.yml:
- Around line 7-9: The top-level workflow permissions currently grant contents:
write and pull-requests: write to every job; restrict these by removing or
reverting the workflow-level write permissions and instead add permissions:
contents: write and pull-requests: write only to the jobs that perform releases
(e.g., the release-please job and the release-upload job). Ensure other jobs
either inherit default read-only permissions or explicitly set minimal
permissions they need (e.g., contents: read) so builders no longer have repo
write access.
- Around line 89-105: The tar command is pointing into packages/js while the
CMake output folder build-wasm is created at the repository root; update the
packaging step so it archives the root-level build-wasm plus the JS files under
packages/js. Replace the current tar invocation (the line that runs tar -czf
img2num-v${release_version}.tar.gz -C packages/js build-wasm index.js ...) with
a command run from the workspace root that explicitly includes build-wasm and
the JS files (e.g., tar -czf img2num-v${release_version}.tar.gz build-wasm
packages/js/index.js packages/js/safeWasmWrappers.js packages/js/wasmClient.js
packages/js/wasmWorker.js), keeping the release_version variable and subsequent
softprops/action-gh-release usage unchanged.
- Around line 37-79: The job checkout and release steps are keyed only to
needs.release-please.outputs.core_tag/core_version which breaks bindings-only
releases; change the job to use the first non-empty release-please output (e.g.
coalesce(needs.release-please.outputs.c_tag,
needs.release-please.outputs.core_tag)) for the checkout ref and for the release
action tag_name, and similarly use
coalesce(needs.release-please.outputs.c_version,
needs.release-please.outputs.core_version) when computing
release_version/ARCHIVE so the job uses the c_* outputs when c_created triggered
and falls back to core_* otherwise (update references to ref, release_version,
and tag_name accordingly).
- Around line 28-29: Replace mutable action tags with immutable commit SHAs for
each third-party action used in the workflow: locate occurrences of
googleapis/release-please-action@v4, softprops/action-gh-release@v2 (all
instances), mymindstorm/setup-emsdk@v14, pypa/cibuildwheel@v2.23.3, and
pypa/gh-action-pypi-publish@release/v1 in the release.yml and update each to the
specific commit SHA from the upstream repo (e.g.,
googleapis/release-please-action@<commit-sha>), keeping existing step IDs (like
id: rp) and parameters unchanged; verify the SHA references by copying the full
commit SHA from each action's GitHub repo and run the workflow lint/check to
ensure no syntax changes are required.
- Around line 53-54: Replace the flow-style YAML mapping used after the checkout
action with a block-style mapping: locate the steps that include "uses:
actions/checkout@v4" and change the inline "with: { ref: ${{
needs.release-please.outputs.core_tag }}, submodules: true }" to a block-style
mapping under with: with keys on their own lines (ref: ${{
needs.release-please.outputs.core_tag }} and submodules: true); apply the same
conversion to the other occurrences mentioned (the other with: flow-style
mappings at the two additional steps) so all uses/... with: blocks follow
block-style YAML for consistency and readability.

In `@release-please-config.json`:
- Around line 3-5: The release tag format currently includes the component name
because "include-component-in-tag": true, producing tags like "core-v0.1.0" that
don't match your docker CI pattern; change "include-component-in-tag" to false
(and you can remove or ignore "tag-separator" if not needed) so release-please
emits plain "vX.Y.Z" tags that match the docker-ci.yml tags: ["v*"] pattern.
- Around line 27-31: The bindings/c entry in release-please-config.json uses
"release-type": "simple" but lacks an extra-files mapping to update the inline
"# x-release-please-version" marker in bindings/c/CMakeLists.txt; add an
"extra-files" array to the bindings/c config that lists
"bindings/c/CMakeLists.txt" (so release-please will replace the
x-release-please-version token) while keeping the existing "release-type":
"simple", "component": "bindings-c" and "package-name": "img2num-c".

---

Outside diff comments:
In @.github/workflows/release.yml:
- Around line 11-140: Add two new GitHub Actions jobs named propagate-core and
propagate-c that run after release-please and create empty commits and PRs to
downstream packages using the RELEASE_PROPAGATION_TOKEN; propagate-core should
run when needs.release-please.outputs.core_created == 'true' and open PRs (one
per repo path: bindings/c, packages/js, packages/py) with a conventional commit
message like "feat: sync with core release", while propagate-c should run when
needs.release-please.outputs.c_created == 'true' AND
needs.release-please.outputs.core_created != 'true' and open a PR only to
packages/js; each job should checkout the repo (actions/checkout@v4), configure
git user, create a unique branch, make an empty commit (git commit --allow-empty
-m "..."), push using the RELEASE_PROPAGATION_TOKEN, and create the PR with gh
pr create (authenticated via the token) so the linked-versions plugin can detect
the commits in the next release cycle.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1687f88c-149e-4cb7-8fad-e873caa87063

📥 Commits

Reviewing files that changed from the base of the PR and between fce52c6 and 48bf9e1.

📒 Files selected for processing (5)
  • .github/workflows/release.yml
  • .release-please-manifest.json
  • bindings/c/CMakeLists.txt
  • core/CMakeLists.txt
  • release-please-config.json
📜 Review details
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Build C/C++ / Build C & C++
  • GitHub Check: Build Documentation Site / Build Docusaurus Site
  • GitHub Check: Build C/C++ / Build WASM (bindings/js)
  • GitHub Check: Build C/C++ / Build Python
  • GitHub Check: Lint & Validate Code
  • GitHub Check: Analyze (c-cpp)
  • GitHub Check: Analyze (actions)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.txt

📄 CodeRabbit inference engine (.editorconfig)

**/*.txt: Do not enforce maximum line length for text files
Use 2-space indentation for text files

Files:

  • bindings/c/CMakeLists.txt
  • core/CMakeLists.txt
bindings/**

⚙️ CodeRabbit configuration file

bindings/**: Language bindings for the Img2Num library. Ensure the public API surface matches
the core C/C++ headers and that error propagation is handled correctly for each
binding language. Also ensure the relevant docstrings are present to enable
auto-generation of documentation.

Files:

  • bindings/c/CMakeLists.txt
**/*.json

📄 CodeRabbit inference engine (.editorconfig)

Do not trim trailing whitespace in JSON files

Files:

  • release-please-config.json
.github/workflows/**

⚙️ CodeRabbit configuration file

.github/workflows/**: GitHub Actions workflows. Review for:

  • SHA-pinned action versions for third-party actions (security best practice).
  • Secrets accessed only via ${{ secrets.* }} — never hardcoded.
  • Least-privilege permissions on each job/workflow.
  • Correct job dependency ordering (needs:) and if/condition logic.

Files:

  • .github/workflows/release.yml
🧠 Learnings (17)
📓 Common learnings
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-25T16:20:34.359Z
Learning: In the Ryan-Millard/Img2Num repository, the following follow-up issues were created after merging PR `#307` (Python bindings): `#328` (Python docstrings/docs), `#329` (numpy img.shape inference for width/height), `#330` (.coderabbit.yaml Python tooling config), `#331` (CI workflow for Python bindings build test).
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:48:02.692Z
Learning: In the Ryan-Millard/Img2Num repository, PR `#311` adds `.coderabbit.yaml` to main. PR `#307` (adding Python .py files) must rebase onto main after `#311` merges so that `.coderabbit.yaml` exists and can be updated with: `ruff: enabled: true`, `pylint: enabled: true`, `flake8: enabled: false` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions via the `./img2num` wrapper. The "CodeRabbit Config Needs Update" custom check will automatically flag `#307` if the Python config is missing.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:48:02.712Z
Learning: In the Ryan-Millard/Img2Num repository, PR `#311` adds `.coderabbit.yaml` to main. PR `#307` (adding Python .py files) must rebase onto main after `#311` merges so that `.coderabbit.yaml` exists and can be updated with: `ruff: enabled: true`, `pylint: enabled: true`, `flake8: enabled: false` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions via the `./img2num` wrapper. The "CodeRabbit Config Needs Update" custom check will automatically flag `#307` if the Python config is missing.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:35:01.229Z
Learning: In the Ryan-Millard/Img2Num repository, Python (.py files) is being added to the codebase via PR `#307`. The `.coderabbit.yaml` should include `ruff: enabled: true` and `pylint: enabled: true` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions via the `./img2num` wrapper.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:35:01.229Z
Learning: In the Ryan-Millard/Img2Num repository, Python (.py files) is being added to the codebase via PR `#307`. The `.coderabbit.yaml` should include `ruff: enabled: true` and `pylint: enabled: true` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-01T20:45:20.732Z
Learning: In the Ryan-Millard/Img2Num repository, a PR body containing generic boilerplate such as "This contribution was made as part of an open-source documentation improvement initiative. All changes are meant to be helpful additions to the project." is a strong signal of an AI-generated slop PR and should be explicitly flagged during review.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T23:16:05.686Z
Learning: In the Img2Num repo, maintainers want to stay on release-please (no manual release management). Any automation we add must feed into release-please’s Release PRs/tags rather than replacing them.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-18T17:26:42.396Z
Learning: In the Ryan-Millard/Img2Num repository, the `.coderabbit.yaml` includes a `pre_merge_checks.custom_checks` entry named "CodeRabbit Config Needs Update" (mode: warning) that checks if a PR introduces new languages, linter config files, new file extensions, or new CI tooling without a corresponding update to `.coderabbit.yaml`.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-25T16:31:13.204Z
Learning: In the Ryan-Millard/Img2Num repository, issue `#331` (CI workflow for Python bindings build test) was addressed in PR `#307` via the `build-py` job in `.github/workflows/cmake-build.yml`. That job runs `uv venv`, `uv sync --no-build-isolation`, a smoke test (`uv run python3 -c "import img2num;"`), and uploads artifacts.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T20:45:56.415Z
Learning: In the Ryan-Millard/Img2Num repository, the release-please monorepo versioning strategy uses the `linked-versions` plugin (not a `dependencies` field, which does not exist in release-please) to keep all four components (core, bindings/c, packages/js, packages/py) at the same version. With `include-component-in-tag: false` and `separate-pull-requests: false`, a single Release PR is created and plain `vX.Y.Z` tags are pushed (compatible with the existing docker-ci.yml `tags: ["v*"]` trigger). Manifest output keys in release-please v4 are path-prefixed: e.g., `core--release_created`, `core--tag_name`. The `core/` and `bindings/c/` paths need a `VERSION` file for `release-type: simple`. CMakeLists.txt line 57 needs a `# x-release-please-version` inline annotation.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T20:52:20.959Z
Learning: In the Ryan-Millard/Img2Num repository, the correct versioning strategy for the release-please monorepo is independent per-path versioning (NOT linked-versions). When core releases (e.g., feat(core)!: major bump), wrappers (bindings/c, packages/js, packages/py) should receive a patch bump, NOT a major bump. Since release-please has no cmake/c/python workspace plugin, the implementation uses a post-release "propagate-core-release" GitHub Actions job that commits fix(bindings/c), fix(packages/js), fix(packages/py) sentinel files to each wrapper path after core releases. This triggers patch release PRs in the next release-please cycle. The propagation commits require a PAT (RELEASE_PROPAGATION_TOKEN) since GITHUB_TOKEN cannot re-trigger workflows. Tags follow include-component-in-tag: false pattern (plain v0.1.0) for docker-ci.yml compatibility.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T20:45:56.415Z
Learning: In the Ryan-Millard/Img2Num repository, the release-please monorepo config uses a `linked-versions` plugin to keep all four components (core, bindings/c, packages/js, packages/py) at the same version. The `"dependencies"` field does not exist in release-please config — cross-package version propagation is handled by the `linked-versions` plugin. Tags follow the `include-component-in-tag: true` pattern (e.g., `core-v0.1.0`), which must be accounted for in docker-ci.yml's tag trigger. The `.release-please-manifest.json` must have keys matching the exact paths: "core", "bindings/c", "packages/js", "packages/py". For `release-type: simple`, a `VERSION` file must exist in each package root (`core/VERSION`, `bindings/c/VERSION`).
📚 Learning: 2026-04-30T20:45:56.415Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T20:45:56.415Z
Learning: In the Ryan-Millard/Img2Num repository, the release-please monorepo config uses a `linked-versions` plugin to keep all four components (core, bindings/c, packages/js, packages/py) at the same version. The `"dependencies"` field does not exist in release-please config — cross-package version propagation is handled by the `linked-versions` plugin. Tags follow the `include-component-in-tag: true` pattern (e.g., `core-v0.1.0`), which must be accounted for in docker-ci.yml's tag trigger. The `.release-please-manifest.json` must have keys matching the exact paths: "core", "bindings/c", "packages/js", "packages/py". For `release-type: simple`, a `VERSION` file must exist in each package root (`core/VERSION`, `bindings/c/VERSION`).

Applied to files:

  • .release-please-manifest.json
  • bindings/c/CMakeLists.txt
  • core/CMakeLists.txt
  • release-please-config.json
  • .github/workflows/release.yml
📚 Learning: 2026-04-30T20:45:56.415Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T20:45:56.415Z
Learning: In the Ryan-Millard/Img2Num repository, the release-please monorepo versioning strategy uses the `linked-versions` plugin (not a `dependencies` field, which does not exist in release-please) to keep all four components (core, bindings/c, packages/js, packages/py) at the same version. With `include-component-in-tag: false` and `separate-pull-requests: false`, a single Release PR is created and plain `vX.Y.Z` tags are pushed (compatible with the existing docker-ci.yml `tags: ["v*"]` trigger). Manifest output keys in release-please v4 are path-prefixed: e.g., `core--release_created`, `core--tag_name`. The `core/` and `bindings/c/` paths need a `VERSION` file for `release-type: simple`. CMakeLists.txt line 57 needs a `# x-release-please-version` inline annotation.

Applied to files:

  • .release-please-manifest.json
  • bindings/c/CMakeLists.txt
  • core/CMakeLists.txt
  • release-please-config.json
  • .github/workflows/release.yml
📚 Learning: 2026-04-30T21:17:08.068Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T21:17:08.068Z
Learning: In Img2Num, manual edits to .release-please-manifest.json after releases are not a reliable way to “sync” release-please. The manifest is read from the last merged Release PR’s commit snapshot; HEAD edits are only honored for bootstrap or when no prior Release PR exists.

Applied to files:

  • .release-please-manifest.json
  • bindings/c/CMakeLists.txt
  • core/CMakeLists.txt
  • release-please-config.json
  • .github/workflows/release.yml
📚 Learning: 2026-04-30T20:52:20.959Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T20:52:20.959Z
Learning: In the Ryan-Millard/Img2Num repository, the correct versioning strategy for the release-please monorepo is independent per-path versioning (NOT linked-versions). When core releases (e.g., feat(core)!: major bump), wrappers (bindings/c, packages/js, packages/py) should receive a patch bump, NOT a major bump. Since release-please has no cmake/c/python workspace plugin, the implementation uses a post-release "propagate-core-release" GitHub Actions job that commits fix(bindings/c), fix(packages/js), fix(packages/py) sentinel files to each wrapper path after core releases. This triggers patch release PRs in the next release-please cycle. The propagation commits require a PAT (RELEASE_PROPAGATION_TOKEN) since GITHUB_TOKEN cannot re-trigger workflows. Tags follow include-component-in-tag: false pattern (plain v0.1.0) for docker-ci.yml compatibility.

Applied to files:

  • .release-please-manifest.json
  • bindings/c/CMakeLists.txt
  • core/CMakeLists.txt
  • release-please-config.json
  • .github/workflows/release.yml
📚 Learning: 2026-04-02T18:39:20.647Z
Learnt from: Krasner
Repo: Ryan-Millard/Img2Num PR: 290
File: core/src/internal/graph.cpp:138-208
Timestamp: 2026-04-02T18:39:20.647Z
Learning: In Ryan-Millard/Img2Num, `add_edge_pixel()` on a `Node` is intentionally designed to allow dual ownership of boundary pixels (the same pixel may appear in the node’s `m_pixels` and also in a neighboring node’s `m_edge_pixels` at the same time). This is required to avoid the SVG “1 pixel boundary problem” (a 1-pixel boundary gap artifact). Treat this as correct behavior: do not review it as a bug, memory/logic misuse, or an invalid data-structure invariant. Note that edge pixels are used only to expand the contour/binary representation for boundary tracing and do not affect node properties such as color.

Applied to files:

  • .release-please-manifest.json
📚 Learning: 2026-04-11T22:21:32.583Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 308
File: Dockerfile.dev:96-97
Timestamp: 2026-04-11T22:21:32.583Z
Learning: In this repo’s development Docker image (Dockerfile.dev), keep the root user as an intentional, acceptable design for dev workflows. Rationale to preserve: pnpm is installed under /root/.local/share/pnpm, /usr/src/app is a host-mounted volume where UID mismatches can cause permission issues, and dev tooling needs root-level flexibility. Do not suggest switching to a non-root USER for Dockerfile.dev unless you can provide a careful, qualified alternative that avoids pnpm path and host-volume permission problems.

Applied to files:

  • .release-please-manifest.json
📚 Learning: 2026-04-25T22:11:19.587Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 327
File: docs/package.json:132-132
Timestamp: 2026-04-25T22:11:19.587Z
Learning: In Ryan-Millard/Img2Num, treat `docusaurus/faster` as a required runtime dependency: ensure it is present in `docs/package.json`. This is needed for Docusaurus 3.10 builds because the project’s `docs/docusaurus.config.js` uses `future: { v4: true }`, enabling `fasterByDefault`, which requires `docusaurus/faster` to be installed; it should not be removed or considered optional for performance in this repo.

Applied to files:

  • .release-please-manifest.json
📚 Learning: 2026-04-30T22:42:14.502Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 338
File: core/CMakeLists.txt:7-10
Timestamp: 2026-04-30T22:42:14.502Z
Learning: In the Ryan-Millard/Img2Num repository, `IMG2NUM_VERSION` is intentionally set to `0.0.0` as a placeholder in `core/CMakeLists.txt`. It will be replaced with the real project version (sourced from `PROJECT_VERSION`) in the upcoming PR for issue `#304`. Do not flag this as an error in the current PR.

Applied to files:

  • bindings/c/CMakeLists.txt
  • core/CMakeLists.txt
📚 Learning: 2026-04-27T15:40:33.329Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 333
File: bindings/c/src/cimg2num.cpp:0-0
Timestamp: 2026-04-27T15:40:33.329Z
Learning: In the Ryan-Millard/Img2Num project, `bindings/c/src/cimg2num.cpp` contains `extern "C"` C bindings. Do NOT suggest throwing C++ exceptions (e.g., `std::bad_alloc`) from within these C binding functions, even from lambdas passed internally to `clear_last_error_and_catch`. The correct C-API idiom is to return `nullptr` (or another sentinel) on failure and let the caller check the return value.

Applied to files:

  • bindings/c/CMakeLists.txt
  • core/CMakeLists.txt
📚 Learning: 2026-04-02T22:50:14.325Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 290
File: bindings/js/CMakeLists.txt:52-53
Timestamp: 2026-04-02T22:50:14.325Z
Learning: In the Img2Num project, `bindings/js/CMakeLists.txt` uses `-fexceptions` (JS-based EH) + `NO_DISABLE_EXCEPTION_CATCHING` in `COMMON_FLAGS` for the `img2num_wasm` target to enable C++ exception handling alongside `ASYNCIFY=1`. `EXCEPTION_CATCHING_ALLOWED` with mangled names is not an acceptable alternative due to architectural concerns. `NO_DISABLE_EXCEPTION_CATCHING` must be present in both Debug and Release builds because `clear_last_error_and_catch()` in `Error.h` is an exception-to-error-code boundary wrapper used in all build configurations.

Applied to files:

  • bindings/c/CMakeLists.txt
  • core/CMakeLists.txt
📚 Learning: 2026-01-06T04:57:04.678Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 191
File: src/wasm/modules/image/src/kmeans.cpp:128-134
Timestamp: 2026-01-06T04:57:04.678Z
Learning: In the Img2Num project, designated initializers (e.g., `RGBXY{.r = ..., .g = ...}`) compile successfully with CMAKE_CXX_STANDARD 17 because the project uses Emscripten/Clang for WASM compilation, which supports designated initializers as a C++17 extension.

Applied to files:

  • bindings/c/CMakeLists.txt
  • core/CMakeLists.txt
📚 Learning: 2026-04-02T22:11:24.408Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 290
File: bindings/js/CMakeLists.txt:52-53
Timestamp: 2026-04-02T22:11:24.408Z
Learning: In the Img2Num project, `bindings/js/CMakeLists.txt` intentionally omits `-fwasm-exceptions` from the `img2num_wasm` target because it is incompatible with `ASYNCIFY=1`, which is set in `COMMON_FLAGS`. Do not suggest re-adding `-fwasm-exceptions` for this target.

Applied to files:

  • bindings/c/CMakeLists.txt
📚 Learning: 2026-04-30T23:16:05.686Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-30T23:16:05.686Z
Learning: In the Img2Num repo, maintainers want to stay on release-please (no manual release management). Any automation we add must feed into release-please’s Release PRs/tags rather than replacing them.

Applied to files:

  • bindings/c/CMakeLists.txt
  • core/CMakeLists.txt
  • release-please-config.json
  • .github/workflows/release.yml
📚 Learning: 2026-04-02T22:20:03.288Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 290
File: bindings/js/CMakeLists.txt:52-53
Timestamp: 2026-04-02T22:20:03.288Z
Learning: In the Img2Num project, `NO_DISABLE_EXCEPTION_CATCHING` (or `-fexceptions` with catching enabled) must be present in both Debug and Release builds for `img2num_wasm`, because `clear_last_error_and_catch()` in `Error.h` catches exceptions at the WASM entry-point boundary to convert them to error codes for JavaScript callers. Removing exception catching from Release would cause uncaught exceptions to abort the WASM module instead of returning error codes. An alternative for Release is `EXCEPTION_CATCHING_ALLOWED` with specific mangled function names to reduce overhead.

Applied to files:

  • bindings/c/CMakeLists.txt
  • core/CMakeLists.txt
📚 Learning: 2026-02-25T21:24:26.232Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 272
File: core/src/internal/bilateral_filter_gpu.cpp:171-185
Timestamp: 2026-02-25T21:24:26.232Z
Learning: In the Img2Num repository, prefer brace initialization syntax (e.g., `Type var{};`) over copy initialization syntax (e.g., `Type var = {};`) when zero-initializing variables, arrays, and structs in C++ code.

Applied to files:

  • bindings/c/CMakeLists.txt
  • core/CMakeLists.txt
📚 Learning: 2026-04-25T16:31:13.204Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-25T16:31:13.204Z
Learning: In the Ryan-Millard/Img2Num repository, issue `#331` (CI workflow for Python bindings build test) was addressed in PR `#307` via the `build-py` job in `.github/workflows/cmake-build.yml`. That job runs `uv venv`, `uv sync --no-build-isolation`, a smoke test (`uv run python3 -c "import img2num;"`), and uploads artifacts.

Applied to files:

  • .github/workflows/release.yml
🪛 actionlint (1.7.12)
.github/workflows/release.yml

[error] 53-53: could not parse as YAML: did not find expected ',' or '}'

(syntax-check)

🪛 GitHub Check: CodeQL
.github/workflows/release.yml

[warning] 28-28: Unpinned tag for a non-immutable Action in workflow
Unpinned 3rd party Action 'Release (release-please, multi-language assets)' step Uses Step: rp uses 'googleapis/release-please-action' with ref 'v4', not a pinned commit hash

🪛 YAMLlint (1.38.0)
.github/workflows/release.yml

[warning] 3-3: truthy value should be one of [false, true]

(truthy)


[error] 5-5: too many spaces inside brackets

(brackets)


[error] 5-5: too many spaces inside brackets

(brackets)


[error] 54-54: too many spaces inside braces

(braces)


[error] 54-54: syntax error: expected ',' or '}', but got '{'

(syntax)


[error] 54-54: too many spaces inside braces

(braces)


[error] 54-54: too many spaces inside braces

(braces)


[error] 88-88: too many spaces inside braces

(braces)


[error] 88-88: too many spaces inside braces

(braces)


[error] 88-88: too many spaces inside braces

(braces)


[error] 88-88: too many spaces inside braces

(braces)


[error] 107-107: too many spaces inside braces

(braces)


[error] 107-107: too many spaces inside braces

(braces)


[error] 121-121: too many spaces inside braces

(braces)


[error] 121-121: too many spaces inside braces

(braces)


[error] 124-124: too many spaces inside braces

(braces)


[error] 124-124: too many spaces inside braces

(braces)


[error] 124-124: too many spaces inside braces

(braces)


[error] 124-124: too many spaces inside braces

(braces)


[error] 126-126: too many spaces inside braces

(braces)


[error] 126-126: too many spaces inside braces

(braces)

Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread release-please-config.json Outdated
Comment thread release-please-config.json
Repository owner deleted a comment from coderabbitai Bot May 1, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/release.yml:
- Around line 161-168: Add packages/js to the release matrix so core-only
releases can propagate to JS by adding an entry with path: packages/js and
scope: js alongside bindings/c and packages/py in the strategy.matrix.target
block, and in the propagate-c job/step add a guard that checks cpp_created !=
'true' (i.e., only open the JS propagation PR when a C++ release did not already
create it) so you don’t double-open the JS PR when both cpp and c releases run.
- Around line 42-83: The job currently coalesces c/cpp outputs using expressions
like needs.release-please.outputs.c_tag || needs.release-please.outputs.cpp_tag
(used in the actions/checkout ref, tag_name input to
softprops/action-gh-release, and release_version computation), which causes one
artifact to be uploaded when both releases are created. Fix by publishing
per-component: fan out over each non-empty release output
(needs.release-please.outputs.c_created and cpp_created) — either convert this
job into a matrix/include that contains one entry for the C release and one for
the C++ release (each entry sets tag = c_tag or cpp_tag and version = c_version
or cpp_version) or add two conditional build/package/upload steps that run only
when the corresponding created flag is true, and pass the specific tag/version
to actions/checkout ref, ARCHIVE naming and softprops/action-gh-release
tag_name/files instead of using the || coalescing expressions.
- Around line 119-143: The current py-wheels-and-pypi job runs
pypa/gh-action-pypi-publish inside a multi-OS matrix which fails on
macOS/Windows; split it into two jobs: (1) a matrix "py-wheels" job (retain name
or use py-wheels) that runs on ubuntu/macos/windows, uses pypa/cibuildwheel and
softprops/action-gh-release or better uploads built wheels with
actions/upload-artifact (referencing the step using pypa/cibuildwheel and files:
wheelhouse/*.whl), and (2) a separate "py-publish" job that runs only on
ubuntu-latest, depends on the first job (needs: py-wheels), downloads artifacts
via actions/download-artifact and then runs pypa/gh-action-pypi-publish
(referencing the existing pypa/gh-action-pypi-publish step) and the release step
if needed; ensure the if condition using needs.release-please.outputs.py_created
and py_tag is preserved on the publish job.
- Around line 74-79: The Windows packaging step uses Bash assignment syntax for
release_version inside a pwsh run block; change that to PowerShell assignment by
creating a PowerShell variable named $release_version (e.g., $release_version =
"<GH expression>") and then use that variable when composing $archive so
Compress-Archive and the Out-File line continue to reference $archive; update
the line that currently reads release_version=... to use $release_version =
"..." (and keep $archive = "img2num-c-cpp-v$release_version-${{ matrix.plat
}}.zip" unchanged).
- Around line 180-207: The workflow currently overwrites the runner's PATH by
using an env var named PATH, which breaks commands like tr, git, cat, and gh;
rename the env var (e.g., TARGET_PATH or MATRIX_PATH) in the release propagation
step and update all references in the script (places that use PATH: the SLUG
assignment, the PR body message, and the dependency note) to that new symbol so
the runner PATH remains intact while still using the matrix-provided path value
for SLUG, BRANCH, git commit message, git push, and gh pr create.

In `@release-please-config.json`:
- Line 3: The release-please config currently sets "separate-pull-requests":
true which causes one PR per package; change this setting to false (set
"separate-pull-requests": false) in release-please-config.json so release-please
produces a single consolidated release PR as requested.
- Around line 18-47: The release-please package keys are aliases (cpp, c,
javascript, python) while manifest mode requires repo-relative directory path
keys; update release-please-config.json to use the package folder paths as
top-level keys (e.g., replace the "cpp" key with "core", "c" with "bindings/c",
"javascript" with "packages/js", "python" with "packages/py") and remove each
redundant "path" property under those entries; then update
.release-please-manifest.json to use matching path-based keys ("core",
"bindings/c", "packages/js", "packages/py") and update
.github/workflows/release.yml outputs that reference alias keys (e.g., replace
"cpp--release_created"→"core--release_created",
"c--release_created"→"bindings/c--release_created",
"javascript--release_created"→"packages/js--release_created",
"python--release_created"→"packages/py--release_created" and similarly for
tag_name/version outputs).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c3347b89-6344-41e9-92b6-54edb0b8f35b

📥 Commits

Reviewing files that changed from the base of the PR and between 48bf9e1 and d44020e.

📒 Files selected for processing (3)
  • .github/workflows/release.yml
  • .release-please-manifest.json
  • release-please-config.json
📜 Review details
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Build C/C++ / Build C & C++
  • GitHub Check: Build C/C++ / Build WASM (bindings/js)
  • GitHub Check: Build C/C++ / Build Python
  • GitHub Check: Build Documentation Site / Build Docusaurus Site
  • GitHub Check: Lint & Validate Code
  • GitHub Check: Analyze (c-cpp)
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.json

📄 CodeRabbit inference engine (.editorconfig)

Do not trim trailing whitespace in JSON files

Files:

  • release-please-config.json
.github/workflows/**

⚙️ CodeRabbit configuration file

.github/workflows/**: GitHub Actions workflows. Review for:

  • SHA-pinned action versions for third-party actions (security best practice).
  • Secrets accessed only via ${{ secrets.* }} — never hardcoded.
  • Least-privilege permissions on each job/workflow.
  • Correct job dependency ordering (needs:) and if/condition logic.

Files:

  • .github/workflows/release.yml
🧠 Learnings (3)
📚 Learning: 2026-04-02T18:39:20.647Z
Learnt from: Krasner
Repo: Ryan-Millard/Img2Num PR: 290
File: core/src/internal/graph.cpp:138-208
Timestamp: 2026-04-02T18:39:20.647Z
Learning: In Ryan-Millard/Img2Num, `add_edge_pixel()` on a `Node` is intentionally designed to allow dual ownership of boundary pixels (the same pixel may appear in the node’s `m_pixels` and also in a neighboring node’s `m_edge_pixels` at the same time). This is required to avoid the SVG “1 pixel boundary problem” (a 1-pixel boundary gap artifact). Treat this as correct behavior: do not review it as a bug, memory/logic misuse, or an invalid data-structure invariant. Note that edge pixels are used only to expand the contour/binary representation for boundary tracing and do not affect node properties such as color.

Applied to files:

  • .release-please-manifest.json
📚 Learning: 2026-04-11T22:21:32.583Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 308
File: Dockerfile.dev:96-97
Timestamp: 2026-04-11T22:21:32.583Z
Learning: In this repo’s development Docker image (Dockerfile.dev), keep the root user as an intentional, acceptable design for dev workflows. Rationale to preserve: pnpm is installed under /root/.local/share/pnpm, /usr/src/app is a host-mounted volume where UID mismatches can cause permission issues, and dev tooling needs root-level flexibility. Do not suggest switching to a non-root USER for Dockerfile.dev unless you can provide a careful, qualified alternative that avoids pnpm path and host-volume permission problems.

Applied to files:

  • .release-please-manifest.json
📚 Learning: 2026-04-25T22:11:19.587Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 327
File: docs/package.json:132-132
Timestamp: 2026-04-25T22:11:19.587Z
Learning: In Ryan-Millard/Img2Num, treat `docusaurus/faster` as a required runtime dependency: ensure it is present in `docs/package.json`. This is needed for Docusaurus 3.10 builds because the project’s `docs/docusaurus.config.js` uses `future: { v4: true }`, enabling `fasterByDefault`, which requires `docusaurus/faster` to be installed; it should not be removed or considered optional for performance in this repo.

Applied to files:

  • .release-please-manifest.json
🪛 actionlint (1.7.12)
.github/workflows/release.yml

[error] 57-57: could not parse as YAML: did not find expected ',' or '}'

(syntax-check)

🪛 GitHub Check: CodeQL
.github/workflows/release.yml

[warning] 28-28: Unpinned tag for a non-immutable Action in workflow
Unpinned 3rd party Action 'Release (release-please, multi-language assets)' step Uses Step: rp uses 'googleapis/release-please-action' with ref 'v4', not a pinned commit hash

🪛 YAMLlint (1.38.0)
.github/workflows/release.yml

[warning] 3-3: truthy value should be one of [false, true]

(truthy)


[error] 5-5: too many spaces inside brackets

(brackets)


[error] 5-5: too many spaces inside brackets

(brackets)


[error] 58-58: too many spaces inside braces

(braces)


[error] 58-58: syntax error: expected ',' or '}', but got '{'

(syntax)


[error] 58-58: too many spaces inside braces

(braces)


[error] 58-58: too many spaces inside braces

(braces)


[error] 92-92: too many spaces inside braces

(braces)


[error] 92-92: too many spaces inside braces

(braces)


[error] 92-92: too many spaces inside braces

(braces)


[error] 92-92: too many spaces inside braces

(braces)


[error] 111-111: too many spaces inside braces

(braces)


[error] 111-111: too many spaces inside braces

(braces)


[error] 125-125: too many spaces inside braces

(braces)


[error] 125-125: too many spaces inside braces

(braces)


[error] 128-128: too many spaces inside braces

(braces)


[error] 128-128: too many spaces inside braces

(braces)


[error] 128-128: too many spaces inside braces

(braces)


[error] 128-128: too many spaces inside braces

(braces)


[error] 130-130: too many spaces inside braces

(braces)


[error] 130-130: too many spaces inside braces

(braces)

Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml Outdated
Comment thread release-please-config.json
Comment thread release-please-config.json
Comment thread .github/workflows/release.yml Fixed
@coderabbitai coderabbitai Bot added the github_actions Pull requests that update GitHub Actions code label May 23, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
.github/workflows/release.yml (3)

187-209: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make propagation PR creation idempotent.

These branches and PR titles are deterministic per release version. A rerun after a partial success will hit an existing branch/PR and fail on git push or gh pr create, which makes recovery brittle.

Suggested guard
           BRANCH="release-propagation/core-v${VERSION}-to-${SLUG}"
+          existing_pr="$(gh pr list --head "$BRANCH" --base main --json number --jq '.[0].number // empty')"
+          if [ -n "$existing_pr" ]; then
+            echo "Propagation PR #$existing_pr already exists for $BRANCH"
+            exit 0
+          fi
 
           git checkout -b "$BRANCH"

Apply the same guard in propagate-c before creating and pushing its branch.

Also applies to: 234-252

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 187 - 209, The PR creation is not
idempotent because BRANCH (constructed as
BRANCH="release-propagation/core-v${VERSION}-to-${SLUG}") may already exist and
cause git push or gh pr create to fail; update the sequence around git checkout
-b "$BRANCH", git push origin "$BRANCH", and gh pr create to first detect an
existing branch or PR: check for a remote branch named "$BRANCH" (e.g., via git
ls-remote or git rev-parse --verify refs/remotes/origin/"$BRANCH") and if found
skip creating/checking out a new branch (or switch to it), and check for an
existing PR with the same title/head using gh pr list (filter by --head
"$BRANCH" or the deterministic title) and if present skip gh pr create; apply
the same guard to the propagate-c block that creates BRANCH and calls git push /
gh pr create so re-runs become no-ops instead of failing.

200-206: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix PR body templating in .github/workflows/release.yml heredocs

The heredoc delimiter is <<'EOF' for both PR bodies, so shell expansion is disabled and ${VERSION} / ${PATH} will be embedded literally in the gh pr create --body text (user-facing defect).

Also, simply switching to <<EOF would re-enable command substitution for the unescaped Markdown backticks (e.g., **\core/` ...`), so the fix should either keep the quoted heredoc and interpolate safely, or unquote it while escaping those backticks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 200 - 206, Change the quoted
heredoc delimiter (<<'EOF') used in the gh pr create --body block so that
${PATH} and ${VERSION} are expanded at runtime and backticks don't trigger
command substitution: replace <<'EOF' with an unquoted heredoc delimiter (<<EOF)
and escape any raw backticks inside the heredoc (e.g., replace ` with \`) so
Markdown inline code remains literal; ensure the gh pr create --body block still
contains ${PATH} and ${VERSION} and test that gh pr create --body receives the
expanded values.

18-26: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix propagation PR creation bodies and rerun idempotency

  • .github/workflows/release.yml output wiring is aligned with release-please-config.json package keys (c, javascript, python, cpp), so the slash-based bindings/c--* / packages/js--* mapping suggestion is not applicable.
  • In propagate-cpp and propagate-c, gh pr create --body "$(cat <<'EOF' ...)" uses a quoted heredoc delimiter (<<'EOF'), so ${VERSION} (and ${PATH} in propagate-cpp) won’t be expanded—PR bodies will contain literal placeholders; change to an unquoted heredoc (e.g., <<EOF) or escape variables.
  • Propagation is non-idempotent: reruns will hit an already-existing PR for the same --head branch and gh pr create errors instead of reusing/updating it; detect an existing PR first (e.g., via gh pr list --head "$BRANCH" --base main) and skip/create-conditionally.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 18 - 26, The workflow outputs in
.github/workflows/release.yml must use the package keys from
release-please-config.json (c, javascript, python, cpp) — ensure the outputs
read c_*, js_*, py_*, cpp_* as appropriate; in propagate-c and propagate-cpp
scripts change the quoted heredoc delimiters used with gh pr create (currently
<<'EOF') to unquoted heredocs (<<EOF) or otherwise escape/expand ${VERSION} and
${PATH} so the PR body contains the expanded values instead of literal
placeholders; finally make propagation idempotent by detecting an existing PR
before running gh pr create (for example call gh pr list --head "$BRANCH" --base
main and if a match exists skip creation or update the existing PR) so reruns
don’t error on duplicate heads.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/py/pyproject.toml`:
- Around line 6-7: The pyproject sets readme = "../../README.md" but the
tool.scikit-build.sdist.include list (tool.scikit-build.sdist.include) does not
include that path, so sdist can miss the README; update the sdist include
entries to add "../../README.md" (or adjust the readme path to a file already
listed) so the README referenced by the readme setting is present in the
sdist/wheel metadata generation; modify the include array in
tool.scikit-build.sdist.include accordingly and keep the readme setting (readme
= "../../README.md") consistent.

---

Outside diff comments:
In @.github/workflows/release.yml:
- Around line 187-209: The PR creation is not idempotent because BRANCH
(constructed as BRANCH="release-propagation/core-v${VERSION}-to-${SLUG}") may
already exist and cause git push or gh pr create to fail; update the sequence
around git checkout -b "$BRANCH", git push origin "$BRANCH", and gh pr create to
first detect an existing branch or PR: check for a remote branch named "$BRANCH"
(e.g., via git ls-remote or git rev-parse --verify
refs/remotes/origin/"$BRANCH") and if found skip creating/checking out a new
branch (or switch to it), and check for an existing PR with the same title/head
using gh pr list (filter by --head "$BRANCH" or the deterministic title) and if
present skip gh pr create; apply the same guard to the propagate-c block that
creates BRANCH and calls git push / gh pr create so re-runs become no-ops
instead of failing.
- Around line 200-206: Change the quoted heredoc delimiter (<<'EOF') used in the
gh pr create --body block so that ${PATH} and ${VERSION} are expanded at runtime
and backticks don't trigger command substitution: replace <<'EOF' with an
unquoted heredoc delimiter (<<EOF) and escape any raw backticks inside the
heredoc (e.g., replace ` with \`) so Markdown inline code remains literal;
ensure the gh pr create --body block still contains ${PATH} and ${VERSION} and
test that gh pr create --body receives the expanded values.
- Around line 18-26: The workflow outputs in .github/workflows/release.yml must
use the package keys from release-please-config.json (c, javascript, python,
cpp) — ensure the outputs read c_*, js_*, py_*, cpp_* as appropriate; in
propagate-c and propagate-cpp scripts change the quoted heredoc delimiters used
with gh pr create (currently <<'EOF') to unquoted heredocs (<<EOF) or otherwise
escape/expand ${VERSION} and ${PATH} so the PR body contains the expanded values
instead of literal placeholders; finally make propagation idempotent by
detecting an existing PR before running gh pr create (for example call gh pr
list --head "$BRANCH" --base main and if a match exists skip creation or update
the existing PR) so reruns don’t error on duplicate heads.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6b9976cc-6960-4e91-82e5-681a96fded8d

📥 Commits

Reviewing files that changed from the base of the PR and between d44020e and ba44824.

📒 Files selected for processing (3)
  • .github/workflows/release.yml
  • packages/js/package.json
  • packages/py/pyproject.toml
📜 Review details
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Build Documentation Site / Build Docusaurus Site
  • GitHub Check: Build C/C++ / Build Python
  • GitHub Check: Build C/C++ / Build WASM (bindings/js)
  • GitHub Check: Build C/C++ / Build C & C++
  • GitHub Check: Lint & Validate Code
  • GitHub Check: Analyze (c-cpp)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.json

📄 CodeRabbit inference engine (.editorconfig)

Do not trim trailing whitespace in JSON files

Files:

  • packages/js/package.json
.github/workflows/**

⚙️ CodeRabbit configuration file

.github/workflows/**: GitHub Actions workflows. Review for:

  • SHA-pinned action versions for third-party actions (security best practice).
  • Secrets accessed only via ${{ secrets.* }} — never hardcoded.
  • Least-privilege permissions on each job/workflow.
  • Correct job dependency ordering (needs:) and if/condition logic.

Files:

  • .github/workflows/release.yml
🧠 Learnings (2)
📚 Learning: 2026-05-01T22:50:11.527Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 339
File: release-please-config.json:18-47
Timestamp: 2026-05-01T22:50:11.527Z
Learning: In this repo, release-please-action v4 preserves '/' verbatim in slash-containing path-based package keys when emitting GitHub Actions output names (e.g., `bindings/c--release_created`). When referencing these step outputs in `job.outputs` (and other expressions), use bracket notation with the exact output name: `${{ steps.release.outputs['bindings/c--release_created'] }}` rather than dot notation. If needed, map the complex step output to a clean job-level output alias so downstream jobs can use dot notation via that alias.

Applied to files:

  • .github/workflows/release.yml
📚 Learning: 2026-05-19T17:30:09.565Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 375
File: .github/workflows/cmake-build.yml:86-88
Timestamp: 2026-05-19T17:30:09.565Z
Learning: In Ryan-Millard/Img2Num CI/workflow YAMLs, any `uv sync` command used for the Python package build must include `--no-build-isolation` (do not remove it). If you need deterministic dependency installs for CI, you may add `--frozen` alongside it (e.g., `uv sync --frozen --no-build-isolation`), and it should not conflict with the repo’s build setup.

Applied to files:

  • .github/workflows/release.yml
🪛 GitHub Check: CodeQL
.github/workflows/release.yml

[warning] 28-28: Unpinned tag for a non-immutable Action in workflow or composite action
Unpinned 3rd party Action 'Release (release-please, multi-language assets)' step Uses Step: rp uses 'googleapis/release-please-action' with ref 'v45996ed1f6d02564a971a2fa1b5860e934307cf7', not a pinned commit hash

🔇 Additional comments (2)
packages/js/package.json (1)

5-7: LGTM!

packages/py/pyproject.toml (1)

22-27: LGTM!

Comment thread packages/py/pyproject.toml Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 24, 2026
@Ryan-Millard
Ryan-Millard changed the base branch from main to dev May 28, 2026 15:48
@Ryan-Millard

Copy link
Copy Markdown
Owner Author

@CodeRabbit full review.

Did I correctly set up the release management in this PR given the recent restructure in #403 and the overall state of npm, PyPI, GitHub, and release-please?

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get PR `#403` details
gh pr view 403 --repo Ryan-Millard/Img2Num --json title,body,files,state,mergedAt 2>/dev/null || echo "PR 403 not found or access denied"

# Also check the current state of all workflow and release config files in this PR branch
echo "=== release-please-config.json ==="
cat release-please-config.json 2>/dev/null || echo "Not found"

echo "=== .release-please-manifest.json ==="
cat .release-please-manifest.json 2>/dev/null || echo "Not found"

echo "=== .github/workflows/release.yml ==="
cat .github/workflows/release.yml 2>/dev/null || echo "Not found"

echo "=== .github/workflows/docker-ci.yml ==="
cat .github/workflows/docker-ci.yml 2>/dev/null || echo "Not found"

echo "=== packages/js/package.json ==="
cat packages/js/package.json 2>/dev/null || echo "Not found"

echo "=== packages/py/pyproject.toml ==="
cat packages/py/pyproject.toml 2>/dev/null || echo "Not found"

echo "=== core/CMakeLists.txt (first 20 lines) ==="
head -20 core/CMakeLists.txt 2>/dev/null || echo "Not found"

echo "=== bindings/c/CMakeLists.txt (first 20 lines) ==="
head -20 bindings/c/CMakeLists.txt 2>/dev/null || echo "Not found"

echo "=== Directory structure ==="
find . -name "*.yml" -path "./.github/*" | sort

Length of output: 32018



@Ryan-Millard — Great work getting the scaffolding in place! After reviewing everything against both the current workflow files and PR #403's restructuring, here is a thorough breakdown.


Overall verdict: Not quite — there are two critical blockers and a handful of important/minor issues


🔴 Critical (will break at release time)

1. packages/py/pyproject.toml was deleted in #403 — release-please can't find it

PR #403 deleted packages/py/pyproject.toml and moved everything to the repo-root pyproject.toml. But release-please-config.json still points to packages/py as a release-type: python package:

"packages/py": {
  "release-type": "python",
  ...
}

Release-please release-type: python looks for pyproject.toml in the directory it's configured for. Since packages/py/pyproject.toml is gone, release-please either silently skips version-bumping or errors out.

Fix options (pick one):

  • Move the package path in release-please-config.json to "." (root) and update the manifest key accordingly — but this conflicts with core/bindings/c also living at the root level.
  • Re-create a minimal packages/py/pyproject.toml that's the entry point for release-please versioning (separate from the scikit-build root config).
  • Use a [tool.release-please] extra-files entry in root pyproject.toml and change the tracked path to . — but only if no other package conflicts with it.

2. Propagation commits use chore(...) — release-please will NOT open a Release PR

git commit --allow-empty \
  -m "chore(${SCOPE}): propagate core v${VERSION} release"

In release-please, only feat, fix, perf, and breaking changes (!) trigger a new release. chore is explicitly hidden and does not open a Release PR. The whole propagation chain breaks silently.

Fix: Change to fix(...):

git commit --allow-empty \
  -m "fix(${SCOPE}): propagate core v${VERSION} release"

Same applies to propagate-c's chore(packages/js): message.


🟠 Important

3. propagate-cpp doesn't directly propagate to packages/js

Your current dependency chain is:

core → propagate-cpp → bindings/c, packages/py
bindings/c → propagate-c → packages/js

So packages/js only gets a bump after the bindings/c propagation PR is merged AND bindings/c releases. This is a two-step lag. Our earlier agreed design had core → {bindings/c, packages/js, packages/py} directly. If you're happy with the two-step chain that's fine — just making sure it's intentional.


4. Propagation still lives in release.yml

You agreed earlier that propagation should be in a separate propagate-release.yml reusable workflow (on: workflow_call) to keep release.yml scoped and clean. That split hasn't been implemented yet.


5. package.json name vs. release-please package-name mismatch

packages/js/package.json:

"name": "img2num"

release-please-config.json:

"packages/js": {
  "package-name": "img2num-js"
}

Release-please derives the npm package name from package.json's name field, not package-name in the config (the config value is used for changelog headers and PR titles). The published npm package will be img2num, not img2num-js. Make sure "name": "img2num" in package.json is the intended npm package name (it may already be taken on the registry).


🟡 Minor

6. CIBW_BUILD is missing Python 3.13

CIBW_BUILD: "cp310-* cp311-* cp312-*"

Python 3.13 has been stable since October 2024. Add cp313-*.


7. imageToUint8ClampedArray.js not included in the release tarball

packages/js/package.json lists imageToUint8ClampedArray.js in its files array, but build-js doesn't include it in the .tar.gz:

tar -czf img2num-v${release_version}.tar.gz \
   build-wasm \
   packages/js/index.js \
   packages/js/safeWasmWrappers.js \
   packages/js/wasmClient.js \
   packages/js/wasmWorker.js
   # ↑ imageToUint8ClampedArray.js is missing

8. PyPI: OIDC vs. API token ambiguity

publish-py has id-token: write (OIDC trusted publishing) but the file header lists PYPI_API_TOKEN as a required secret. pypa/gh-action-pypi-publish will prefer OIDC when id-token: write is present and no token is explicitly passed. If you intend to use OIDC, you don't need PYPI_API_TOKEN (and should remove it from the required-secrets comments + repo secrets docs). If you intend to use the API token, remove id-token: write. Pick one.


9. build-c-cpp-native runs unnecessary matrix legs

When only c_created == 'true' (not cpp_created), the job still spins up 3 runners × 2 components = 6 runners, with the cpp legs doing nothing because the steps are guarded. Consider splitting into two separate jobs (one per component) or filtering the matrix dynamically.


Summary table

# Severity Issue
1 🔴 Critical packages/py/pyproject.toml deleted in #403; release-please can't find/bump Python version
2 🔴 Critical Propagation commits use chore → release-please won't open Release PRs
3 🟠 Important propagate-cpp doesn't directly propagate to packages/js (two-step lag)
4 🟠 Important Propagation not split to propagate-release.yml as agreed
5 🟠 Important package.json name img2num ≠ release-please package-name: img2num-js
6 🟡 Minor cp313-* missing from CIBW_BUILD
7 🟡 Minor imageToUint8ClampedArray.js missing from JS release tarball
8 🟡 Minor PyPI OIDC vs. API token — pick one approach
9 🟡 Minor build-c-cpp-native wastes runner minutes on non-released components

The two blockers (items 1 and 2) need to be resolved before merging — everything else can be a follow-up.


✏️ Learnings added
Learnt from: Ryan-Millard
URL: https://github.com/Ryan-Millard/Img2Num/pull/339

Timestamp: 2026-05-28T22:30:07.569Z
Learning: In Ryan-Millard/Img2Num, PR `#403` (merged 2026-05-28) deleted `packages/py/pyproject.toml` and moved the Python package build config to the repo-root `pyproject.toml`. As of that merge, the Python scikit-build-core config lives at `pyproject.toml` (root), not `packages/py/pyproject.toml`. The release-please config for the `packages/py` package needs to reflect this restructuring.

Learnt from: Ryan-Millard
URL: https://github.com/Ryan-Millard/Img2Num/pull/339

Timestamp: 2026-05-28T22:30:07.569Z
Learning: In Ryan-Millard/Img2Num, release propagation commits in `.github/workflows/release.yml` must use `fix(...)` conventional commit type (not `chore(...)`), because release-please only opens Release PRs for `feat`, `fix`, `perf`, and breaking changes. `chore` is hidden and does not trigger a release.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Actions performed

Full review triggered.

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

@coderabbitai here is my response to yours:

1. packages/py/pyproject.toml was deleted in #403 — release-please can't find it

PR #403 deleted packages/py/pyproject.toml and moved everything to the repo-root pyproject.toml. But release-please-config.json still points to packages/py as a release-type: python package:

I have no idea what to do for that. There is no clean solution and it seems like I'm not allowed anything nice. Please show me what you're planning (it might also be weird to end up doing, though).

2. Propagation commits use chore(...) — release-please will NOT open a Release PR

Won't it just create a patch PR?

🟠 Important

3. propagate-cpp doesn't directly propagate to packages/js

Your current dependency chain is:

core → propagate-cpp → bindings/c, packages/py
bindings/c → propagate-c → packages/js

So packages/js only gets a bump after the bindings/c propagation PR is merged AND bindings/c releases. This is a two-step lag. Our earlier agreed design had core → {bindings/c, packages/js, packages/py} directly. If you're happy with the two-step chain that's fine — just making sure it's intentional.

We need that. In the real world, a project that has a dependency with sub-dependencies only has the update when the sub-dependency is updated in the dependency. This is wanted and good.

4. Propagation still lives in release.yml

You agreed earlier that propagation should be in a separate propagate-release.yml reusable workflow (on: workflow_call) to keep release.yml scoped and clean. That split hasn't been implemented yet.

I changed my mind. It shouldn't.

5. package.json name vs. release-please package-name mismatch

packages/js/package.json:

"name": "img2num"

release-please-config.json:

"packages/js": {
  "package-name": "img2num-js"
}

Release-please derives the npm package name from package.json's name field, not package-name in the config (the config value is used for changelog headers and PR titles). The published npm package will be img2num, not img2num-js. Make sure "name": "img2num" in package.json is the intended npm package name (it may already be taken on the registry).

I don't trust you there. I'd rather die in battle that blindly follow your lead here. Maybe I'll fix it later if necessary.

6. CIBW_BUILD is missing Python 3.13

CIBW_BUILD: "cp310-* cp311-* cp312-*"

Python 3.13 has been stable since October 2024. Add cp313-*.

To what? Don't suggest abstract code ideas ever again. Always say where it should go.

9. build-c-cpp-native runs unnecessary matrix legs

When only c_created == 'true' (not cpp_created), the job still spins up 3 runners × 2 components = 6 runners, with the cpp legs doing nothing because the steps are guarded. Consider splitting into two separate jobs (one per component) or filtering the matrix dynamically.

Show me what you suggest.

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== .github/workflows/release.yml ==="
cat .github/workflows/release.yml

echo ""
echo "=== pyproject.toml (root) ==="
cat pyproject.toml 2>/dev/null || echo "Not found at root"

echo ""
echo "=== packages/py/pyproject.toml ==="
cat packages/py/pyproject.toml 2>/dev/null || echo "Not found"

echo ""
echo "=== release-please-config.json ==="
cat release-please-config.json

Length of output: 16891



@Ryan-Millard — here are the concrete answers to each point.


Issue 1 — packages/py/pyproject.toml missing after #403

The least-weird solution is a two-part fix:

Part A — create a minimal stub packages/py/pyproject.toml that release-please owns (it is not used for building — the root one is):

# packages/py/pyproject.toml
# This file exists solely for release-please version tracking.
# The authoritative build config is the root pyproject.toml.
[project]
name = "img2num"
version = "0.0.0"

Part B — tell release-please to also bump the root pyproject.toml by adding an extra-files entry and a one-line annotation to the root file.

In release-please-config.json, change the packages/py block to:

"packages/py": {
  "release-type": "python",
  "component": "packages-py",
  "package-name": "img2num-py",
  "extra-files": [
    { "type": "generic", "path": "pyproject.toml" }
  ]
}

In the root pyproject.toml, add the annotation comment so release-please knows which line to bump:

version = "0.0.0"  # x-release-please-version

Result: when release-please opens a Python Release PR, it bumps packages/py/pyproject.toml (via release-type: python) and the root pyproject.toml (via extra-files generic). The build process only ever reads the root file, so nothing breaks.


Issue 2 — chore will NOT open a Release PR

Look at your own release-please-config.json:

{ "type": "chore", "section": "🔧 Miscellaneous", "hidden": true }

hidden: true means release-please discards that commit when computing whether a new release is needed. The propagation PR lands on main, release-please sees it, finds only chore commits — and does nothing. The entire chain silently dies there.

Change both propagation jobs from chorefix:

In propagate-cpp:

# Before
git commit --allow-empty \
  -m "chore(${SCOPE}): propagate core v${VERSION} release"
# After
git commit --allow-empty \
  -m "fix(${SCOPE}): propagate core v${VERSION} release"

And the gh pr create --title to match:

--title "fix(${SCOPE}): propagate core v${VERSION} release" \

Same change in propagate-c:

# Before
git commit --allow-empty \
  -m "chore(packages/js): propagate bindings/c v${VERSION} release"
# After
git commit --allow-empty \
  -m "fix(js): propagate bindings/c v${VERSION} release"

Issue 3 — Two-step chain is intentional

Noted and agreed. The dependency chain core → bindings/c → packages/js reflects reality.


Issue 4 — Propagation stays in release.yml

Fine, no split needed.


Issue 6 — cp313-* exact location

In .github/workflows/release.yml, inside the build-py job:

      - uses: pypa/cibuildwheel@a78478edc4b83cce46f23e49e649d3e4b6437df3
        with:
          package-dir: .
          output-dir: wheelhouse
        env:
          CIBW_BUILD: "cp310-* cp311-* cp312-*"           # ← change this line
          CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28

Change to:

          CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-*"

Issue 9 — Split build-c-cpp-native into two jobs

Replace the single matrix job with two independent jobs. No more guards, no wasted runners:

  build-cpp-native:
    needs: release-please
    if: ${{ needs.release-please.outputs.cpp_created == 'true' }}
    runs-on: ${{ matrix.os }}
    permissions:
      contents: write
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        include:
          - os: ubuntu-latest
            plat: linux-x64
          - os: macos-latest
            plat: macos-arm64
          - os: windows-latest
            plat: windows-x64
    steps:
      - uses: actions/checkout@900f2210b1d28bbbd0bd22d17926b9e224e8f231
        with:
          ref: ${{ needs.release-please.outputs.cpp_tag }}
          submodules: true

      - name: Configure
        run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install

      - name: Build
        run: cmake --build build --config Release

      - name: Install
        run: cmake --install build --config Release

      - name: Package (Linux/macOS)
        if: runner.os != 'Windows'
        run: |
          ARCHIVE="img2num-cpp-v${{ needs.release-please.outputs.cpp_version }}-${{ matrix.plat }}.tar.gz"
          tar -czf "$ARCHIVE" -C install .
          echo "ARCHIVE=$ARCHIVE" >> $GITHUB_ENV

      - name: Package (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          $archive = "img2num-cpp-v${{ needs.release-please.outputs.cpp_version }}-${{ matrix.plat }}.zip"
          Compress-Archive -Path install\* -DestinationPath $archive
          "ARCHIVE=$archive" | Out-File -FilePath $env:GITHUB_ENV -Append

      - uses: softprops/action-gh-release@403a5240f3837fa857f642062e05aad6bb3391ca
        with:
          tag_name: ${{ needs.release-please.outputs.cpp_tag }}
          files: ${{ env.ARCHIVE }}

  build-c-native:
    needs: release-please
    if: ${{ needs.release-please.outputs.c_created == 'true' }}
    runs-on: ${{ matrix.os }}
    permissions:
      contents: write
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        include:
          - os: ubuntu-latest
            plat: linux-x64
          - os: macos-latest
            plat: macos-arm64
          - os: windows-latest
            plat: windows-x64
    steps:
      - uses: actions/checkout@900f2210b1d28bbbd0bd22d17926b9e224e8f231
        with:
          ref: ${{ needs.release-please.outputs.c_tag }}
          submodules: true

      - name: Configure
        run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install

      - name: Build
        run: cmake --build build --config Release

      - name: Install
        run: cmake --install build --config Release

      - name: Package (Linux/macOS)
        if: runner.os != 'Windows'
        run: |
          ARCHIVE="img2num-c-v${{ needs.release-please.outputs.c_version }}-${{ matrix.plat }}.tar.gz"
          tar -czf "$ARCHIVE" -C install .
          echo "ARCHIVE=$ARCHIVE" >> $GITHUB_ENV

      - name: Package (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          $archive = "img2num-c-v${{ needs.release-please.outputs.c_version }}-${{ matrix.plat }}.zip"
          Compress-Archive -Path install\* -DestinationPath $archive
          "ARCHIVE=$archive" | Out-File -FilePath $env:GITHUB_ENV -Append

      - uses: softprops/action-gh-release@403a5240f3837fa857f642062e05aad6bb3391ca
        with:
          tag_name: ${{ needs.release-please.outputs.c_tag }}
          files: ${{ env.ARCHIVE }}

The two blockers are items 1 and 2 — both have concrete fixes above.

(╥_╥) 🐇


✏️ Learnings added
Learnt from: Ryan-Millard
URL: https://github.com/Ryan-Millard/Img2Num/pull/339

Timestamp: 2026-05-28T22:42:52.726Z
Learning: In Ryan-Millard/Img2Num, release propagation jobs (propagate-cpp, propagate-c) stay inside `.github/workflows/release.yml` — NOT in a separate reusable workflow file. Ryan explicitly changed his mind about splitting to a `propagate-release.yml` reusable workflow.

Learnt from: Ryan-Millard
URL: https://github.com/Ryan-Millard/Img2Num/pull/339

Timestamp: 2026-05-28T22:42:52.726Z
Learning: In Ryan-Millard/Img2Num, `packages/py/pyproject.toml` was deleted in PR `#403` (merged 2026-05-28) and the Python build config moved to the repo-root `pyproject.toml`. The fix for release-please (which needs a pyproject.toml at the configured path) is: (1) create a minimal stub `packages/py/pyproject.toml` with `[project] name="img2num" version="0.0.0"` for release-please tracking only; (2) add `extra-files: [{type: generic, path: pyproject.toml}]` to the `packages/py` block in `release-please-config.json`; (3) annotate the root `pyproject.toml` version line with `# x-release-please-version`. The build process only reads the root pyproject.toml; the stub is purely for release-please.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

Issue 9 — Split build-c-cpp-native into two jobs

Replace the single matrix job with two independent jobs. No more guards, no wasted runners:

  build-cpp-native:
    needs: release-please
    if: ${{ needs.release-please.outputs.cpp_created == 'true' }}
    runs-on: ${{ matrix.os }}
    permissions:
      contents: write
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        include:
          - os: ubuntu-latest
            plat: linux-x64
          - os: macos-latest
            plat: macos-arm64
          - os: windows-latest
            plat: windows-x64
    steps:
      - uses: actions/checkout@900f2210b1d28bbbd0bd22d17926b9e224e8f231
        with:
          ref: ${{ needs.release-please.outputs.cpp_tag }}
          submodules: true

      - name: Configure
        run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install

      - name: Build
        run: cmake --build build --config Release

      - name: Install
        run: cmake --install build --config Release

      - name: Package (Linux/macOS)
        if: runner.os != 'Windows'
        run: |
          ARCHIVE="img2num-cpp-v${{ needs.release-please.outputs.cpp_version }}-${{ matrix.plat }}.tar.gz"
          tar -czf "$ARCHIVE" -C install .
          echo "ARCHIVE=$ARCHIVE" >> $GITHUB_ENV

      - name: Package (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          $archive = "img2num-cpp-v${{ needs.release-please.outputs.cpp_version }}-${{ matrix.plat }}.zip"
          Compress-Archive -Path install\* -DestinationPath $archive
          "ARCHIVE=$archive" | Out-File -FilePath $env:GITHUB_ENV -Append

      - uses: softprops/action-gh-release@403a5240f3837fa857f642062e05aad6bb3391ca
        with:
          tag_name: ${{ needs.release-please.outputs.cpp_tag }}
          files: ${{ env.ARCHIVE }}

  build-c-native:
    needs: release-please
    if: ${{ needs.release-please.outputs.c_created == 'true' }}
    runs-on: ${{ matrix.os }}
    permissions:
      contents: write
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        include:
          - os: ubuntu-latest
            plat: linux-x64
          - os: macos-latest
            plat: macos-arm64
          - os: windows-latest
            plat: windows-x64
    steps:
      - uses: actions/checkout@900f2210b1d28bbbd0bd22d17926b9e224e8f231
        with:
          ref: ${{ needs.release-please.outputs.c_tag }}
          submodules: true

      - name: Configure
        run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install

      - name: Build
        run: cmake --build build --config Release

      - name: Install
        run: cmake --install build --config Release

      - name: Package (Linux/macOS)
        if: runner.os != 'Windows'
        run: |
          ARCHIVE="img2num-c-v${{ needs.release-please.outputs.c_version }}-${{ matrix.plat }}.tar.gz"
          tar -czf "$ARCHIVE" -C install .
          echo "ARCHIVE=$ARCHIVE" >> $GITHUB_ENV

      - name: Package (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          $archive = "img2num-c-v${{ needs.release-please.outputs.c_version }}-${{ matrix.plat }}.zip"
          Compress-Archive -Path install\* -DestinationPath $archive
          "ARCHIVE=$archive" | Out-File -FilePath $env:GITHUB_ENV -Append

      - uses: softprops/action-gh-release@403a5240f3837fa857f642062e05aad6bb3391ca
        with:
          tag_name: ${{ needs.release-please.outputs.c_tag }}
          files: ${{ env.ARCHIVE }}

@coderabbitai, I think I'll leave this implementation for some other time. I'd rather have things working right away than have advanced logic that makes things harder to maintain at first.

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

@coderabbitai full review.

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@Ryan-Millard Totally fair — the current guarded-matrix job works correctly, it just burns a few extra runner minutes. Getting a solid, working release pipeline first and optimising later is the right call.

Would you like me to open a follow-up issue to track the split of build-c-cpp-native into build-cpp-native and build-c-native so it doesn't get forgotten?

(。•́︿•̀。) 🐇

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

@Ryan-Millard Triggering a full review of the PR now!

✅ Actions performed

Full review triggered.

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

@coderabbitai does this get your stamp of approval?

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== packages/py/pyproject.toml (stub?) ==="
cat packages/py/pyproject.toml 2>/dev/null || echo "NOT FOUND"

echo ""
echo "=== release-please-config.json ==="
cat release-please-config.json

echo ""
echo "=== root pyproject.toml (check for x-release-please-version annotation) ==="
grep -n "x-release-please-version\|^version" pyproject.toml 2>/dev/null || echo "NOT FOUND or no annotation"

echo ""
echo "=== .github/workflows/release.yml — propagation commit messages ==="
grep -n "chore\|fix.*propagate\|allow-empty" .github/workflows/release.yml

echo ""
echo "=== .github/workflows/release.yml — CIBW_BUILD line ==="
grep -n "CIBW_BUILD" .github/workflows/release.yml

echo ""
echo "=== packages/js/package.json — files array ==="
cat packages/js/package.json

echo ""
echo "=== .github/workflows/release.yml — JS tarball packaging step ==="
grep -n "imageToUint8\|tar.*img2num\|pkg.*js" .github/workflows/release.yml

echo ""
echo "=== .github/workflows/release.yml — id-token and PYPI lines ==="
grep -n "id-token\|PYPI_API_TOKEN\|pypi-publish\|password:" .github/workflows/release.yml

Length of output: 5220



@Ryan-Millard — yes, you've earned it. 🐇 ✅

Here's a quick run-through of everything I checked:

# Item Status
1 🔴 packages/py/pyproject.toml stub created ✅ Present with correct content
1 🔴 release-please-config.json extra-files → root pyproject.toml ✅ Present
1 🔴 Root pyproject.toml # x-release-please-version annotation ✅ Line 3
2 🔴 Propagation commits changed from chorefix ✅ Both propagate-cpp and propagate-c
6 🟡 cp313-* added to CIBW_BUILD ✅ Done
7 🟡 imageToUint8ClampedArray.js in JS tarball ✅ Line 154

One lingering note before you merge:

PyPI OIDC setup: id-token: write is present and no API token is passed explicitly to pypa/gh-action-pypi-publish, so it will use OIDC trusted publishing — which is the better approach. Just make sure you've configured Trusted Publishing for this repository on PyPI's side (under the img2num project settings → Publishing → Trusted publishers → GitHub Actions). If that's not done, the publish step will fail at release time. PYPI_API_TOKEN can then be removed from the required-secrets list in the PR description.

Everything else was either fixed or deliberately deferred. You're good to merge. 🐇

[approve]

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

@coderabbitai generate a commit message and description for this.

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@Ryan-Millard Here's a commit message and description you can use:


Commit message (subject line)

chore(ci): add Release Please automation with multi-language publish and propagation

Commit description (body)

Set up automated semantic versioning and release management using
googleapis/release-please-action@v4, replacing manual version management.

Release-please config (`release-please-config.json`) defines four independent
packages with per-path release types and emoji changelog sections:
  - core          (simple / C++)
  - bindings/c    (simple / C)
  - packages/js   (node)
  - packages/py   (python)

Manifest (`.release-please-manifest.json`) bootstraps all packages at 0.0.0.

GitHub Actions workflow (`.github/workflows/release.yml`) runs on every push
to `main` and:

  Build & publish jobs (triggered only on release creation):
  - build-c-cpp-native  — CMake build → platform archives uploaded to GitHub
                          Release (Linux/macOS .tar.gz, Windows .zip) for both
                          core and bindings/c.
  - build-js            — Emscripten WASM build → tar.gz to GitHub Release +
                          npm publish via OIDC / NPM_TOKEN.
  - build-py            — cibuildwheel wheels (cp310–cp313, all platforms) →
                          GitHub Release + PyPI via OIDC trusted publishing.

  Propagation jobs (create PRs, never commit directly to main):
  - propagate-cpp  — fires when core releases; opens fix() PRs for
                     bindings/c and packages/py via RELEASE_PROPAGATION_TOKEN.
  - propagate-c    — fires when bindings/c releases independently (guard
                     prevents double-bumping packages/js when core already
                     propagated); opens a fix() PR for packages/js.

  Dependency graph:
    core ──► bindings/c
      │
      └────► packages/py
    bindings/c ──► packages/js

CMakeLists.txt files in core/ and bindings/c/ annotated with
`# x-release-please-version` so release-please can bump the VERSION line.

packages/py/pyproject.toml stub added for release-please version tracking
(build config remains at repo-root pyproject.toml, annotated with
`# x-release-please-version`).

packages/js/package.json: added `publishConfig.access: "public"`.

.github/workflows/docker-ci.yml: tag filter updated from `v*` to `*-v*`
to match the new include-component-in-tag format (e.g. core-v1.2.3).

Required secrets before merging:
  - RELEASE_PROPAGATION_TOKEN  (PAT with contents + pull-requests write)
  - NPM_TOKEN
  - PYPI_API_TOKEN (or configure OIDC Trusted Publishing on PyPI)

Closes `#304`

Feel free to trim the body if you want it shorter. The subject line follows conventional commits (chore(ci):) since this is pure CI/tooling infrastructure. 🐇

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

Labels

build-system c/c++ Changes to C or C++ files ci core github_actions Pull requests that update GitHub Actions code js/ts python changes to python bindings or python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(releases): set up automated Semantic Versioning and releases

2 participants