Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ jobs:
id: changes
env:
GITHUB_EVENT_NAME: ${{ github.event_name }}
GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }}
GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: rust-script scripts/detect-code-changes.rs

# === CHANGELOG CHECK - only runs on PRs with code changes ===
Expand Down Expand Up @@ -121,11 +119,16 @@ jobs:
GITHUB_BASE_REF: ${{ github.base_ref }}
run: rust-script scripts/check-version-modification.rs

# Lint runs independently of changelog check - it's a fast check that should always run
# See: https://github.com/link-assistant/hive-mind/pull/1024 for why this dependency was removed
# === LINT AND FORMAT CHECK ===
lint:
name: Lint and Format Check
runs-on: ubuntu-latest
needs: [detect-changes]
# Note: always() is required because detect-changes is skipped on workflow_dispatch,
# and without always(), this job would also be skipped even though its condition includes workflow_dispatch.
# See: https://github.com/actions/runner/issues/491
if: |
always() && !cancelled() && (
github.event_name == 'push' ||
Expand Down Expand Up @@ -168,11 +171,13 @@ jobs:
- name: Check file size limit
run: rust-script scripts/check-file-size.rs

# Test runs independently of changelog check
# === TEST ===
test:
name: Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
needs: [detect-changes, changelog]
# Run if: push event, OR changelog succeeded, OR changelog was skipped (docs-only PR)
if: always() && !cancelled() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || needs.changelog.result == 'success' || needs.changelog.result == 'skipped')
strategy:
fail-fast: false
Expand Down Expand Up @@ -203,6 +208,7 @@ jobs:
- name: Run doc tests
run: cargo test --doc --verbose

# Generate and upload code coverage using cargo-llvm-cov
# === CODE COVERAGE ===
coverage:
name: Code Coverage
Expand Down Expand Up @@ -248,6 +254,7 @@ jobs:
files: lcov.info
fail_ci_if_error: false

# Build package - only runs if lint and test pass
# === BUILD ===
build:
name: Build Package
Expand Down Expand Up @@ -279,10 +286,13 @@ jobs:
- name: Check package
run: cargo package -p doublets --list --allow-dirty

# Automatic release on push to main using changelog fragments
# This job automatically bumps version based on fragments in changelog.d/
# === AUTO RELEASE ===
auto-release:
name: Auto Release
needs: [lint, test, build]
# Note: always() ensures consistent behavior with other jobs that depend on jobs using always().
if: |
always() && !cancelled() &&
github.event_name == 'push' &&
Expand Down Expand Up @@ -346,16 +356,20 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Use new_version from version-and-commit when available (tag-checked), else fall back to Cargo.toml version
RELEASE_VERSION="${{ steps.version.outputs.new_version }}"
if [ -z "$RELEASE_VERSION" ]; then
RELEASE_VERSION="${{ steps.current_version.outputs.version }}"
fi
rust-script scripts/create-github-release.rs --release-version "$RELEASE_VERSION" --repository "${{ github.repository }}"

# Manual release via workflow_dispatch - only after CI passes
# === MANUAL INSTANT RELEASE ===
manual-release:
name: Instant Release
needs: [lint, test, build]
# Note: always() is required to evaluate the condition when dependencies use always().
# The build job ensures lint and test passed before this job runs.
if: |
always() && !cancelled() &&
github.event_name == 'workflow_dispatch' &&
Expand Down Expand Up @@ -457,6 +471,7 @@ jobs:
2. Merge this PR to main
3. The automated release workflow will publish to crates.io and create a GitHub release

# Deploy Rust API documentation to GitHub Pages after a successful release
# === DEPLOY DOCUMENTATION ===
deploy-docs:
name: Deploy Rust Documentation
Expand Down
9 changes: 9 additions & 0 deletions changelog.d/issue-49-workspace-cicd-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
bump: patch
---

### Fixed
- Fixed CI/CD release pipeline failing on Cargo workspace repositories
- All release scripts now correctly resolve the publishable member crate's Cargo.toml instead of reading the workspace root manifest
- Fixed `bump-version.rs` failing on versions with pre-release suffixes (e.g. `0.1.0-pre+beta.15`)
- Fixed `publish-crate.rs` to pass `-p <package>` flag for workspace repos
78 changes: 78 additions & 0 deletions docs/case-studies/issue-49/analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Case Study: CI/CD Publishing Failed (Issue #49)

## Timeline of Events

1. **2026-04-14T15:13:22Z** - Push to `main` branch triggers CI/CD pipeline (run [#24406907038](https://github.com/linksplatform/doublets-rs/actions/runs/24406907038))
2. **2026-04-14T15:13:22Z - 15:14:10Z** - Jobs `Detect Changes`, `Lint and Format Check`, `Code Coverage`, `Test` (all platforms), `Build Package` pass successfully
3. **2026-04-14T15:14:10Z** - `Auto Release` job starts, passes setup steps (checkout, rust toolchain, rust-script, git config, bump type detection)
4. **2026-04-14T15:14:34Z** - **FAILURE** at step "Check if version already released or no fragments"
- Error: `Could not find name in ./Cargo.toml`
- Exit code: 1

## Root Cause Analysis

### Primary Root Cause: Workspace Cargo.toml incompatibility

The repository uses a **Cargo workspace** structure:

```
./Cargo.toml # [workspace] only - NO [package] section
./doublets/Cargo.toml # [package] with name="doublets", version="0.1.0-pre+beta.15"
./integration/Cargo.toml # [package] with publish=false
```

All CI/CD release scripts (`check-release-needed.rs`, `version-and-commit.rs`, `publish-crate.rs`, `get-version.rs`, `bump-version.rs`, `collect-changelog.rs`, `create-github-release.rs`, `check-version-modification.rs`) were designed for **single-crate repositories** where the root `Cargo.toml` contains both `[package]` and `[workspace]` sections, or is purely a `[package]`.

The scripts use regex `^name\s*=\s*"([^"]+)"` to extract the crate name from root `Cargo.toml`, which fails when the root is a workspace-only manifest.

### Secondary Root Cause: Pre-release version regex in bump-version.rs

The `bump-version.rs` script uses regex `^version\s*=\s*"(\d+)\.(\d+)\.(\d+)"` which requires the version string to end immediately after the patch number. The actual version `0.1.0-pre+beta.15` has a pre-release suffix, causing the regex to not match.

## Affected Components

| Script | Reads Name | Reads Version | Writes Version | Impact |
|--------|-----------|---------------|----------------|--------|
| check-release-needed.rs | Yes | Yes | No | **Blocks all releases** |
| version-and-commit.rs | Yes | Yes | Yes | Blocks version bumping |
| publish-crate.rs | Yes | Yes | No | Blocks crates.io publishing |
| get-version.rs | No | Yes | No | Blocks version reporting |
| bump-version.rs | No | Yes | Yes | Blocks version bumping |
| collect-changelog.rs | No | Yes | No | Blocks changelog generation |
| create-github-release.rs | Yes (optional) | No | No | Missing badges in releases |
| check-version-modification.rs | No | No | No | Checks wrong file for diffs |
| rust-paths.rs | No | No | No | Missing workspace info |

## Solution

Added workspace-aware `Cargo.toml` resolution to all affected scripts:

1. **Detection**: Check if root `Cargo.toml` contains `[workspace]` section
2. **Member parsing**: Extract `members = [...]` list from workspace manifest
3. **Publishable member selection**: Find the first member whose `Cargo.toml` does NOT contain `publish = false`
4. **Path resolution**: Return the publishable member's `Cargo.toml` path instead of root

Additionally:
- Fixed `bump-version.rs` regex to handle pre-release version suffixes
- Added `-p <package>` flag to `cargo publish` command in `publish-crate.rs` for workspace repos

## Verification

All scripts tested locally against the actual workspace structure:

```
$ rust-script scripts/check-release-needed.rs
Detected workspace Cargo.toml, searching for publishable member...
Using publishable workspace member: doublets (doublets/Cargo.toml)
Crate: doublets, Version: 0.1.0-pre+beta.15, Published on crates.io: true
```

## Template Repository Impact

- **Rust template** (`link-foundation/rust-ai-driven-development-pipeline-template`): Same bug confirmed. All 5 release scripts lack workspace awareness. Issue filed: https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/issues/36
- **JS template** (`link-foundation/js-ai-driven-development-pipeline-template`): Not affected. npm/pnpm workspace root `package.json` files still contain `name` and `version`, and the template uses `@changesets/cli` with native workspace support.

## CI Logs

- Full run log: `ci-logs/run-24406907038-full.log`
- Failed step log: `ci-logs/run-24406907038-failed.log`
15 changes: 15 additions & 0 deletions docs/case-studies/issue-49/ci-logs/run-24406907038-failed.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2704245Z ##[group]Run rust-script scripts/check-release-needed.rs
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2704915Z rust-script scripts/check-release-needed.rs
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2725778Z shell: /usr/bin/bash -e {0}
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2726103Z env:
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2726409Z CARGO_TERM_COLOR: always
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2726680Z RUSTFLAGS: -Dwarnings
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2782405Z CARGO_REGISTRY_TOKEN: ***
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2782920Z CARGO_TOKEN: ***
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2783238Z CARGO_HOME: /home/runner/.cargo
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2783607Z CARGO_INCREMENTAL: 0
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2784175Z HAS_FRAGMENTS: true
Auto Release Check if version already released or no fragments 2026-04-14T15:14:10.2784871Z ##[endgroup]
Auto Release Check if version already released or no fragments 2026-04-14T15:14:34.5924661Z Detected single-language repository (Cargo.toml in root)
Auto Release Check if version already released or no fragments 2026-04-14T15:14:34.5926800Z Error: Could not find name in ./Cargo.toml
Auto Release Check if version already released or no fragments 2026-04-14T15:14:34.5943244Z ##[error]Process completed with exit code 1.
Loading
Loading