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
40 changes: 29 additions & 11 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,23 @@ jobs:

# === LINT AND FORMAT CHECK ===
# Lint runs independently of changelog check - it's a fast check that should always run
# 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
lint:
name: Lint and Format Check
runs-on: ubuntu-latest
needs: [detect-changes]
if: |
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.rs-changed == 'true' ||
needs.detect-changes.outputs.toml-changed == 'true' ||
needs.detect-changes.outputs.mjs-changed == 'true' ||
needs.detect-changes.outputs.docs-changed == 'true' ||
needs.detect-changes.outputs.workflow-changed == 'true'
always() && !cancelled() && (
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.rs-changed == 'true' ||
needs.detect-changes.outputs.toml-changed == 'true' ||
needs.detect-changes.outputs.mjs-changed == 'true' ||
needs.detect-changes.outputs.docs-changed == 'true' ||
needs.detect-changes.outputs.workflow-changed == 'true'
)
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -166,7 +171,8 @@ jobs:
runs-on: ${{ matrix.os }}
needs: [detect-changes, changelog]
# Run if: push event, OR changelog succeeded, OR changelog was skipped (docs-only PR)
if: always() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || needs.changelog.result == 'success' || needs.changelog.result == 'skipped')
# Note: always() is required to evaluate the condition when dependencies are skipped.
if: always() && !cancelled() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || needs.changelog.result == 'success' || needs.changelog.result == 'skipped')
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -200,7 +206,8 @@ jobs:
name: Build Package
runs-on: ubuntu-latest
needs: [lint, test]
if: always() && needs.lint.result == 'success' && needs.test.result == 'success'
# Note: always() ensures this job runs even when lint/test jobs use always().
if: always() && !cancelled() && needs.lint.result == 'success' && needs.test.result == 'success'
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -230,7 +237,13 @@ jobs:
auto-release:
name: Auto Release
needs: [lint, test, build]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
# 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 == 'push' &&
github.ref == 'refs/heads/main' &&
needs.build.result == 'success'
runs-on: ubuntu-latest
permissions:
contents: write
Expand Down Expand Up @@ -310,7 +323,12 @@ jobs:
manual-release:
name: Manual Release
needs: [lint, test, build]
if: github.event_name == 'workflow_dispatch'
# 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' &&
needs.build.result == 'success'
runs-on: ubuntu-latest
permissions:
contents: write
Expand Down
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Issue to solve: https://github.com/link-foundation/browser-commander/issues/27
Your prepared branch: issue-27-4fd8f05681d7
Your prepared working directory: /tmp/gh-issue-solver-1768607156411

Proceed.
116 changes: 116 additions & 0 deletions docs/case-studies/issue-27/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Case Study: Rust Release Jobs Skipped (Issue #27)

## Timeline of Events

### 2026-01-16 17:09:55 UTC - Run ID 21074589083
- **Event**: `workflow_dispatch` (manual trigger)
- **Result**: Pipeline completed but release jobs were skipped
- **Jobs Status**:
- Detect Changes: SKIPPED (expected - has `if: github.event_name != 'workflow_dispatch'`)
- Test (all platforms): SUCCESS
- Changelog Fragment Check: SKIPPED
- **Lint and Format Check: SKIPPED** (unexpected)
- **Build Package: SKIPPED** (unexpected - depends on lint)
- **Manual Release: SKIPPED** (unexpected - depends on build)
- Auto Release: SKIPPED (expected - only on push to main)

### 2026-01-10 13:38:44 UTC - Run ID 20879147120
- **Event**: `push` to main branch
- **Result**: Build succeeded but Auto Release was skipped
- **Jobs Status**:
- Detect Changes: SUCCESS
- Lint and Format Check: SUCCESS
- Test (all platforms): SUCCESS
- Build Package: SUCCESS
- **Auto Release: SKIPPED** (unexpected)

## Root Cause Analysis

### Primary Root Cause: Missing `always()` in Job Conditions

The GitHub Actions workflow has a fundamental issue with job dependency evaluation. When a job is skipped, all jobs that depend on it are also skipped by default unless they use `always()` in their condition.

**The Problematic Pattern (current):**
```yaml
lint:
needs: [detect-changes]
if: |
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.rs-changed == 'true' ||
...
```

**The Correct Pattern (from template):**
```yaml
lint:
needs: [detect-changes]
if: |
always() && !cancelled() && (
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.rs-changed == 'true' ||
...
)
```

### Chain Reaction

1. On `workflow_dispatch`, `detect-changes` is skipped (by design)
2. Without `always()`, `lint` job is automatically skipped when its dependency is skipped
3. `build` depends on `lint`, so it's also skipped
4. `manual-release` depends on `build`, so it's also skipped

### Secondary Root Cause: Inconsistent Condition for Auto Release

The `auto-release` job has the condition:
```yaml
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
```

But it lacks `always() && !cancelled()` prefix and `needs.build.result == 'success'` verification, which can cause issues when upstream jobs use `always()`.

## Solution

### 1. Add `always() && !cancelled()` to All Dependent Jobs

Jobs that depend on `detect-changes` need the pattern:
```yaml
if: |
always() && !cancelled() && (
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
...
)
```

This ensures:
- `always()` - Job runs even when dependencies are skipped
- `!cancelled()` - Job doesn't run if workflow was cancelled
- The actual condition determines if job should run

### 2. Add Result Verification for Release Jobs

Release jobs should verify upstream jobs succeeded:
```yaml
auto-release:
if: |
always() && !cancelled() &&
github.event_name == 'push' &&
github.ref == 'refs/heads/main' &&
needs.build.result == 'success'
```

## Reference

- [GitHub Actions: Jobs that use `always()` need dependencies to also use it](https://github.com/actions/runner/issues/491)
- [Template Repository Best Practices](https://github.com/link-foundation/rust-ai-driven-development-pipeline-template)

## Affected Files

1. `.github/workflows/rust.yml` - Main workflow file needing fixes
2. `rust/scripts/get-bump-type.mjs` - Currently works with `rust/changelog.d`, needs update for monorepo structure

## Fix Implementation

See the PR for the complete fix that aligns with the template repository best practices.
47 changes: 47 additions & 0 deletions docs/case-studies/issue-27/job-summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# CI Job Summary for Issue #27

## Run 21074589083 (workflow_dispatch, 2026-01-16)

| Job | Conclusion |
|-----|------------|
| Detect Changes | skipped |
| Test (macos-latest) | success |
| Test (windows-latest) | success |
| Test (ubuntu-latest) | success |
| Changelog Fragment Check | skipped |
| Lint and Format Check | **skipped** |
| Build Package | **skipped** |
| Manual Release | **skipped** |
| Auto Release | skipped |

**Problem**: Lint, Build, and Manual Release were unexpectedly skipped due to missing `always()` in conditions.

## Run 20879147120 (push to main, 2026-01-10)

| Job | Conclusion |
|-----|------------|
| Detect Changes | success |
| Lint and Format Check | success |
| Test (ubuntu-latest) | success |
| Test (windows-latest) | success |
| Test (macos-latest) | success |
| Changelog Fragment Check | skipped |
| Build Package | success |
| Auto Release | **skipped** |
| Manual Release | skipped |

**Problem**: Auto Release was unexpectedly skipped even though all upstream jobs passed because:
1. The `auto-release` condition lacked `always()` prefix
2. The `auto-release` condition didn't verify `needs.build.result == 'success'`

## Fix Applied

Updated `.github/workflows/rust.yml` to add `always() && !cancelled()` and result verification to:
- `lint` job
- `test` job
- `build` job
- `auto-release` job
- `manual-release` job

This matches the pattern used in the reference template:
https://github.com/link-foundation/rust-ai-driven-development-pipeline-template