Skip to content

UN-3408 [MISC] Add automated release workflow and version validation#1915

Merged
jaseemjaskp merged 2 commits into
mainfrom
misc/UN-3408-MISC_release_workflow_validation
Apr 16, 2026
Merged

UN-3408 [MISC] Add automated release workflow and version validation#1915
jaseemjaskp merged 2 commits into
mainfrom
misc/UN-3408-MISC_release_workflow_validation

Conversation

@muhammad-ali-e
Copy link
Copy Markdown
Contributor

@muhammad-ali-e muhammad-ali-e commented Apr 16, 2026

What

  • Add new .github/workflows/create-release.yaml workflow for automated release creation
    • workflow_dispatch inputs: version_bump (patch/minor/major), confirm_major (gate), pre_release, dry_run
    • Auto-fetches latest release, computes next version, validates monotonic increment
    • Creates tag + GitHub release, which triggers existing production-build workflow
    • Concurrency group prevents race conditions on simultaneous runs
  • Add validate-tag job to .github/workflows/production-build.yaml
    • Validates tag format (^v[0-9]+\.[0-9]+\.[0-9]+$) and version increment
    • build-and-push blocked via needs: validate-tag if validation fails

Why

Between v0.152.0 and v0.158.0, a manual typo on the GitHub release page caused versions to jump from v0.152.0v1.53.0, progress through v1.57.1, and revert to v0.158.0. Six accidentally-created v1.x tags now exist on the repo.

Since unstract-cloud fetches the latest OSS release frequently for RC creation, a bad OSS release can be picked up by cloud RCs before anyone notices. This PR introduces an automated release path to eliminate typos, plus a validation safety net.

Slack thread: https://zipstack.slack.com/archives/C07TXK5239Q/p1776234492314379

How

  • create-release.yaml uses the GitHub App token pattern (same as unstract-cloud/create-rc-build.yaml) because the default GITHUB_TOKEN cannot trigger downstream workflows
  • Version computation: parses vX.Y.Z, increments based on bump type, validates via sort -V -C
  • validate-tag runs as a separate job so build-and-push gets needs: validate-tag — bad tags block the build
  • Major version changes are blocked in both workflows: confirm_major gate in create-release, unexpected-major-change error in validate-tag

Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)

No. The validation job is strict but only fails on malformed or regressing tags — valid releases pass through unchanged. The existing build-and-push flow is untouched apart from the needs: validate-tag dependency. Existing workflow_dispatch manual triggers with correctly-formatted tags continue to work.

Database Migrations

None.

Env Config

The following must be configured on the OSS repo for create-release.yaml to work:

  • Variable: PUSH_TO_MAIN_APP_ID
  • Secret: PUSH_TO_MAIN_APP_PRIVATE_KEY
  • GitHub App with contents: write permission installed on the repo

Without these, only the validate-tag changes in production-build.yaml take effect.

Relevant Docs

Related Issues or PRs

Dependencies Versions

None.

Notes on Testing

  1. Happy path: Run Create Release workflow with patch → verify next version computed correctly (e.g. v0.163.2v0.163.3), tag + release created, production build triggered.
  2. Dry run: dry_run = true → summary shows computed version, no tag created.
  3. Major bump gate: version_bump = major with confirm_major = false → fails with clear error.
  4. Bad manual release: Create release with v1.200.0 via UI → validate-tag blocks the build.
  5. Malformed dispatch tag: Trigger production-build with tag = \"0.164.0\" → fails format check.

Follow-up (manual config, outside this PR)

Set up tag protection ruleset at Settings → Rules → Rulesets:

  • Target: Tags, Pattern: v*
  • Restrict creations/updates/deletions
  • Bypass: release bot GitHub App + admins

This is the primary defense — it closes the race window for cloud-RC consumption by preventing bad tags at the source.

Checklist

  • I have added an appropriate PR title and description
  • I have read and understood the Contribution Guidelines
  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • New and existing unit tests pass locally with my changes

🤖 Generated with Claude Code

Add create-release.yaml for automated release creation with version_bump
input (patch/minor/major), confirm_major gate, and dry_run mode.

Add validate-tag job to production-build.yaml that blocks Docker builds
on malformed tags or unexpected version increments.

Prevents recurrence of the v0.152.0 -> v1.53.0 typo incident.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

Summary by CodeRabbit

  • New Features

    • Added a manual release workflow to create releases with selectable bump types, pre-release and dry-run options.
  • Chores

    • Improved release controls including explicit confirmation for major bumps and branch-mode handling.
    • Build pipeline now waits for tag validation before packaging and publishing.
  • Bug Fixes

    • Stronger tag/version validation to prevent invalid or duplicate releases and misleading build summaries.

Walkthrough

Adds a new manual "Create Release" GitHub Actions workflow that computes and publishes semantic-version releases with validation and dry-run support, and augments the production-build workflow with a validate-tag job that enforces tag format and strict semantic increment before build/push and summaries.

Changes

Cohort / File(s) Summary
Release Creation Workflow
.github/workflows/create-release.yaml
New workflow Create Release with workflow_dispatch inputs (version_bump, confirm_major, pre_release, dry_run). Generates a GitHub App token, validates branch and bump rules (hotfix/major checks), derives base tag, computes next semver, checks for tag collisions, supports dry-run, and creates release via gh release create with generated notes.
Production Build Validation
.github/workflows/production-build.yaml
Added validate-tag job that extracts and validates vX.Y.Z tag from release event or inputs.tag, finds prior same-line tag when applicable, and enforces the new tag is strictly greater. build-and-push now needs: validate-tag; build-summary now needs both and only runs when validation succeeded. Tag exposure moved to env before writing to GITHUB_ENV.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Actions as "GitHub Actions\nRunner"
  participant AppToken as "Create GitHub App\nToken (actions/create-github-app-token)"
  participant GitHubAPI as "GitHub API / Releases"
  participant GHCLI as "gh CLI"

  User->>Actions: workflow_dispatch (version_bump, confirm_major, pre_release, dry_run)
  Actions->>AppToken: request app token
  AppToken-->>Actions: app token
  Actions->>Actions: validate branch name (main or vX.Y.Z-hotfix)
  Actions->>GitHubAPI: fetch base tag (latest or matching hotfix line)
  GitHubAPI-->>Actions: base tag (or error)
  Actions->>Actions: compute next semver (patch/minor/major), enforce confirm_major
  Actions->>GitHubAPI: check if computed tag already exists
  GitHubAPI-->>Actions: tag exists? (yes/no)
  alt dry_run == true
    Actions->>Actions: write Dry Run Summary
  else
    Actions->>GHCLI: gh release create --notes-start-tag <prev> --target <ref> [--prerelease] [--latest]
    GHCLI-->>GitHubAPI: create release
    GitHubAPI-->>GHCLI: release created
    GHCLI-->>Actions: success
    Actions->>Actions: write Release Created summary
  end
Loading
sequenceDiagram
  participant Trigger as "Release/Event\nor workflow_dispatch"
  participant Actions as "GitHub Actions\nRunner"
  participant GitHubAPI as "GitHub API / Releases"
  participant Build as "build-and-push"
  participant Summary as "build-summary"

  Trigger->>Actions: start production-build workflow
  Actions->>GitHubAPI: (validate-tag) extract tag from event/inputs, validate ^vX.Y.Z$
  GitHubAPI-->>Actions: prior tags (if needed)
  Actions->>Actions: enforce new tag > prior tag (or skip if none)
  alt validate-tag success
    Actions->>Build: proceed with build-and-push (needs validated tag)
    Build-->>Actions: build/push results
    Actions->>Summary: generate summary (only if validation succeeded)
  else
    Actions-->>Trigger: fail early (validation error) — downstream blocked
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: adding automated release workflow and version validation to prevent malformed releases.
Description check ✅ Passed The description comprehensively covers all template sections with detailed explanations of what was added, why it was needed, how it works, testing notes, and configuration requirements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch misc/UN-3408-MISC_release_workflow_validation

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.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 16, 2026

Greptile Summary

This PR introduces a create-release.yaml workflow for automated, bot-driven GitHub release creation and a validate-tag job in production-build.yaml that gates build-and-push on tag-format and same-line monotonicity checks, addressing the accidental v1.x tag series. The same-line validation approach cleanly resolves the earlier conflict between validate-tag unconditionally blocking major bumps and create-release.yaml intentionally supporting them.

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 improvements and do not block the primary automated release path.

Previous P1 concerns (major-bump self-referential loop, sort-V-C equality gap, unused checkout) appear resolved in the current revision. The same-line validation approach sidesteps the major-bump conflict entirely, and the equality guard was added in create-release.yaml. Two new P2 findings remain: the collision check running unconditionally in dry-run mode (cosmetic UX issue) and the cross-line regression gap in validate-tag (mitigated by the recommended tag protection ruleset).

Minor attention to create-release.yaml collision check dry-run guard and production-build.yaml cross-line validation gap — neither blocks merge.

Important Files Changed

Filename Overview
.github/workflows/create-release.yaml New workflow for automated release creation; solid overall (App token, concurrency lock, collision guard, dry-run), but the collision check runs unconditionally even in dry-run mode, causing dry runs to fail instead of showing the summary when the computed tag already exists as a pre-release.
.github/workflows/production-build.yaml Adds validate-tag job with format + same-line monotonicity checks that gate build-and-push; the same-line approach cleanly resolves the earlier major-bump conflict, but a cross-line regression (e.g., v0.100.0 after v0.163.x) would pass validation since no prior releases exist on the new line.

Sequence Diagram

sequenceDiagram
    actor Dev
    participant CRW as create-release.yaml
    participant GHA as GitHub API
    participant PBW as production-build.yaml

    Dev->>CRW: workflow_dispatch (version_bump, dry_run, pre_release)
    CRW->>CRW: Validate branch (main / vX.Y.Z-hotfix)
    CRW->>CRW: Validate major confirmation gate
    CRW->>GHA: Fetch latest release tag
    GHA-->>CRW: LATEST_TAG
    CRW->>CRW: Compute NEW_TAG (patch/minor/major bump)
    CRW->>CRW: Equality + sort-V-C monotonicity check
    CRW->>GHA: Check for tag collision (gh release view NEW_TAG)
    alt dry_run == true
        CRW->>CRW: Output Dry Run Summary (no release created)
    else
        CRW->>GHA: gh release create NEW_TAG (App token)
        GHA-->>PBW: release:created event
        PBW->>PBW: validate-tag format check
        PBW->>GHA: gh release list (same major.minor line)
        GHA-->>PBW: PREV_TAG
        PBW->>PBW: Same-line monotonicity check
        alt validation passes
            PBW->>PBW: build-and-push (matrix: 7 services)
            PBW->>PBW: build-summary + Slack notification
        else validation fails
            PBW->>PBW: build-and-push blocked
        end
    end
Loading

Fix All in Claude Code

Prompt To Fix All With AI
This is a comment left during a code review.
Path: .github/workflows/create-release.yaml
Line: 177-188

Comment:
**Collision check runs unconditionally in dry-run mode**

The `Check for tag collision` step has no `if: github.event.inputs.dry_run != 'true'` guard. If the computed tag already exists as a pre-release or draft, the workflow exits with "Delete it first or pick a different bump type" and the `Dry run summary` step never executes — defeating the purpose of the dry run. The PR description states that dry-run should "show computed version, no tag created," which implies the summary should always be reached.

```suggestion
      - name: Check for tag collision
        id: collision-check
        if: github.event.inputs.dry_run != 'true'
        env:
          GH_TOKEN: ${{ steps.generate-token.outputs.token }}
          NEW_TAG: ${{ steps.compute-version.outputs.new_tag }}
        run: |
          # Guard against collisions with existing pre-releases or drafts
          # (which are hidden by --exclude-pre-releases in the earlier lookup).
          if gh release view "$NEW_TAG" --repo "${{ github.repository }}" >/dev/null 2>&1; then
            echo "::error::A release with tag '$NEW_TAG' already exists (possibly as a pre-release or draft). Delete it first or pick a different bump type."
            exit 1
          fi
```

Alternatively, include a warning line about the collision in the dry-run summary itself.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: .github/workflows/production-build.yaml
Line: 64-72

Comment:
**Same-line check allows cross-line regressions to pass**

The `PREV_TAG` lookup filters by the same `NEW_MAJOR_MINOR` line, and when no prior release exists on that line the step exits 0 with "new minor/major bump." This means a manually created tag like `v0.100.0` would pass `validate-tag` when the current latest is `v0.163.5`, because there are no prior releases on `v0.100.x`. Until the recommended tag-protection ruleset is in place (noted as a follow-up outside this PR), `validate-tag` is the last line of defence against regressive manual releases.

A cross-line monotonicity check against the global latest would close the gap:

```bash
# After same-line check passes, also verify the new tag is >= the global latest
GLOBAL_LATEST=$(gh api "repos/${{ github.repository }}/releases/latest" --jq '.tag_name // empty')
if [[ -n "$GLOBAL_LATEST" && "$GLOBAL_LATEST" != "$NEW_TAG" ]]; then
  GLOBAL_VER="${GLOBAL_LATEST#v}"
  if printf '%s\n%s\n' "$NEW_VER" "$GLOBAL_VER" | sort -V -C; then
    echo "::error::New version $NEW_TAG is less than or equal to global latest $GLOBAL_LATEST."
    exit 1
  fi
fi
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (2): Last reviewed commit: "UN-3408 [MISC] Address PR review: hotfix..." | Re-trigger Greptile

Comment thread .github/workflows/production-build.yaml Outdated
Comment thread .github/workflows/production-build.yaml Outdated
Comment thread .github/workflows/production-build.yaml
Comment thread .github/workflows/create-release.yaml Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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/create-release.yaml:
- Around line 67-72: The workflow's "Fetch latest release version" step (id:
get-latest) uses gh release list without pre-releases, which causes LATEST_TAG
to ignore pre-release tags and leads gh release create to fail when promoting a
pre-release; update the get-latest logic that sets LATEST_TAG (the gh release
list invocation) to either include pre-releases in the query or add a new
branch: detect an existing tag first (e.g., query all releases or tags via gh
release list --include-pre-releases or gh api to list tags), and if a matching
pre-release tag exists skip creating a duplicate or perform a promotion flow
instead of creating the same tag; ensure the subsequent gh release create call
checks for the tag's existence before attempting to create it.

In @.github/workflows/production-build.yaml:
- Around line 43-44: The "Validate version increment" step currently only runs
when github.event_name == 'release', which lets workflow_dispatch bypass the
check; update the step's conditional so the validation runs for both release and
manual triggers (e.g., change the if on the "Validate version increment" step to
run when github.event_name == 'release' OR github.event_name ==
'workflow_dispatch') so manual workflow_dispatch runs cannot skip the
monotonicity validation.
- Around line 66-69: The validate-tag job in production-build.yaml currently
rejects any major bump by comparing NEW_MAJOR and PREV_MAJOR unconditionally;
change it to skip the major-version rejection when the workflow was triggered by
a release.created event that includes the originating approval signal
(confirm_major=true) from create-release.yaml. Update the condition around the
NEW_MAJOR vs PREV_MAJOR check (in the validate-tag job) to first detect
github.event_name == 'release' and then inspect github.event.release.body (or a
chosen release field/label) for the confirm_major=true marker coming from
create-release.yaml; only enforce the fatal exit if no such approval marker is
present (i.e., default behavior unchanged for pushes and unapproved releases).
- Around line 50-51: The PREV_TAG lookup uses gh release list with
--exclude-pre-releases which hides recent pre-releases and makes .[1] point to
the wrong baseline; update the gh release list invocation used to compute
PREV_TAG so it does not exclude pre-releases (remove the --exclude-pre-releases
flag) and still excludes drafts, ensuring the returned list includes the latest
pre-release so .[1].tagName correctly references the previous release whether it
was a pre-release or stable.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a53bf7a2-8603-4b99-b978-26589be0b373

📥 Commits

Reviewing files that changed from the base of the PR and between 6383b10 and cb60f51.

📒 Files selected for processing (2)
  • .github/workflows/create-release.yaml
  • .github/workflows/production-build.yaml

Comment thread .github/workflows/create-release.yaml Outdated
Comment thread .github/workflows/production-build.yaml Outdated
Comment thread .github/workflows/production-build.yaml Outdated
Comment thread .github/workflows/production-build.yaml Outdated
Copy link
Copy Markdown
Contributor

@jaseemjaskp jaseemjaskp left a comment

Choose a reason for hiding this comment

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

PR Review Summary

Critical Issues (3)

1. jq returns literal "null" string, bypassing empty check

File: production-build.yaml — "Validate version increment" step

When only one release exists, .[1].tagName returns null, which jq -r converts to the string "null" — not an empty string. The -z "$PREV_TAG" check won't catch it, and subsequent cut operations will parse "null" as a version, producing a misleading error about major version changes.

Fix: Use // empty in the jq expression:

PREV_TAG=$(gh release list ... --jq '.[1].tagName // empty')

2. sort -V -C allows equal versions (both files)

Files: create-release.yaml (~line 119), production-build.yaml (~line 62)

sort -V -C checks non-decreasing order, so equal versions pass. Combined with the missing default case in the case statement (see Suggestion 1 below), an unknown bump type could leave the version unchanged and the check would not catch it.

Fix: Add an explicit equality check:

if [[ "$CURRENT_NUM" == "$NEW_NUM" ]] || ! printf '%s\n%s\n' "$CURRENT_NUM" "$NEW_NUM" | sort -V -C; then
  echo "::error::Version $NEW_TAG is not strictly greater than $LATEST_TAG"
  exit 1
fi

3. Race condition / ordering assumption with gh release list

Files: create-release.yaml (Fetch latest), production-build.yaml (Validate version increment)

gh release list returns releases ordered by creation date, not semver. If a backport or hotfix is created after a newer release, positional indexing (.[0], .[1]) returns the wrong version.

Fix for create-release.yaml — use GitHub's "latest" API:

LATEST_TAG=$(gh api repos/${{ github.repository }}/releases/latest --jq '.tag_name')

Fix for production-build.yaml — filter out the current tag:

PREV_TAG=$(gh release list --limit 10 --exclude-drafts --exclude-pre-releases --json tagName \
  --jq "[.[] | select(.tagName != \"$NEW_TAG\")] | .[0].tagName // empty")

Important Issues (5)

4. Summary step runs even when release creation fails

File: create-release.yaml — "Summary" step

The if: github.event.inputs.dry_run != 'true' condition replaces the default success() check. If gh release create fails, the summary still writes "Release Created" to GITHUB_STEP_SUMMARY.

Fix: if: github.event.inputs.dry_run != 'true' && success()

5. Major version block will reject legitimate major bumps from Create Release workflow

File: production-build.yaml — "Validate version increment" step

A user confirms the major bump via confirm_major in create-release.yaml, which creates a release — but then production-build.yaml fires on the release: created event and blocks the build because NEW_MAJOR != PREV_MAJOR. The two workflows contradict each other.

Fix: Either remove the major-version block from production-build.yaml (rely on the Create Release workflow's gate), or have the validate-tag step detect that the release was created by the release bot and skip the major check.

6. No branch restriction — releases can be cut from any branch

File: create-release.yaml

--target "${{ github.ref_name }}" uses whatever branch the workflow is dispatched from.

Fix: Add an early validation step:

- name: Validate branch
  if: github.ref_name != 'main'
  run: |
    echo "::error::Releases must be created from the 'main' branch."
    exit 1

7. Script injection surface on github.event.inputs.tag

File: production-build.yaml — existing "Set version tag" step

${{ github.event.inputs.tag }} is interpolated directly into a run: block. While validate-tag now catches bad formats, if the needs dependency is ever removed, this becomes exploitable.

Fix: Use an env: block instead of direct interpolation:

env:
  TAG: ${{ github.event.release.tag_name || github.event.inputs.tag }}
run: echo "DOCKER_VERSION_TAG=$TAG" >> $GITHUB_ENV

8. build-summary runs with misleading output when validate-tag fails

File: production-build.yaml

build-summary has if: always() and needs: build-and-push. When validate-tag fails, build-and-push is skipped, but build-summary still runs and may produce misleading output.

Fix: Add validate-tag to build-summary's needs and gate on its success:

build-summary:
  needs: [validate-tag, build-and-push]
  if: always() && needs.validate-tag.result == 'success'

Suggestions (5)

  1. Add default case in case statement (create-release.yaml — "Compute next version"): If triggered via API with an unexpected bump type, no increment occurs. Add *) echo "::error::Unknown bump type: $BUMP_TYPE"; exit 1 ;;

  2. Add set -euo pipefail (both files): Use job-level defaults to prevent silent pipeline failures:

    defaults:
      run:
        shell: bash -euo pipefail {0}
  3. Validate PREV_TAG format (production-build.yaml): Add a vX.Y.Z regex check on PREV_TAG before parsing with cut.

  4. Add comment explaining GitHub App token (create-release.yaml): The default GITHUB_TOKEN can't trigger downstream workflows — this rationale should be captured inline, not just in the PR description.

  5. Update "Safety-net" comment (production-build.yaml): The tag protection ruleset doesn't exist yet (it's a follow-up), so this job is currently the only protection, not "defense-in-depth". Suggest rewording to reflect the current state.


Strengths

  • Core design is sound — automating release creation to eliminate manual typos addresses the real problem well
  • Concurrency group with cancel-in-progress: false is the right choice for releases
  • Dry run support is well thought out
  • Error messages include specific values and actionable guidance
  • PR description is exceptionally thorough

…on, safety fixes

- create-release.yaml: branch-aware mode (main vs vX.Y.Z-hotfix), uses
  gh api releases/latest for main (immune to hotfix creation-date ordering),
  same-line lookup for hotfixes, explicit --latest flag per Hotfix Guide
- Pre-release/draft collision check before creating a release
- Equal-version guard (sort -V -C alone treats equal as valid)
- Default case in bump type switch
- Summary step gated on success()
- Removed unused checkout step
- set -euo pipefail via job defaults
- production-build.yaml: same-line increment check (handles hotfixes
  correctly without contradicting create-release major bumps)
- jq null -> empty via '// empty' pattern
- workflow_dispatch now also validates increment (was bypass)
- build-summary gated on validate-tag success
- Set version tag uses env block (avoids user-input interpolation in run)
- PREV_TAG format regex check before parsing

Addresses review feedback from jaseemjaskp, coderabbitai, greptile-apps on
PR #1915.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@muhammad-ali-e
Copy link
Copy Markdown
Contributor Author

Review feedback addressed — commit a274f13

Thanks for the reviews @jaseemjaskp, @coderabbitai, @greptile-apps. All concerns validated against the actual code (several confirmed via jq/sort tests) and resolved in one follow-up commit. Also aligned with the Hotfix Deployment Guide to preserve the existing hotfix flow.

Critical fixes

Reviewer Issue Resolution
jaseemjaskp #1 jq -r returns literal "null" string — -z check misses it All jq lookups use // empty
jaseemjaskp #2, greptile P2 sort -V -C accepts equal versions Explicit if [[ "$PREV_VER" == "$NEW_VER" ]] guard in both files
jaseemjaskp #3 gh release list sorts by creation date — breaks with hotfixes from older lines create-release uses gh api releases/latest on main (respects explicit latest flag); validate-tag uses same-major.minor-line lookup
greptile P1, coderabbitai Critical, jaseemjaskp #5 Major bump in create-release blocked by validate-tag Removed unconditional major block; now uses same-line comparison so legitimate major bumps pass

Correctness / robustness

Reviewer Issue Resolution
jaseemjaskp #4 Summary step prints "Release Created" even on failure if: success() && ...
jaseemjaskp #6 No branch restriction Accepts only main or vX.Y.Z-hotfix branches
jaseemjaskp #7 Script injection surface on Set version tag Uses env: block instead of direct interpolation
jaseemjaskp #8 build-summary runs with misleading output when validation fails needs: [validate-tag, build-and-push] + gated on validate-tag success
coderabbitai workflow_dispatch bypasses increment check Removed the github.event_name == 'release' guard; validates on both paths
coderabbitai Pre-release tag collision in create-release New Check for tag collision step using gh release view (catches pre-releases and drafts)
coderabbitai, greptile P2 Pre-release baseline wrong (--exclude-pre-releases shifts index) Resolved by switching to gh api releases/latest + same-line lookup
greptile Unused checkout step Removed — all operations use the API, no local files

Suggestions (all applied)

  • Default case in bump-type switch with clear error
  • set -euo pipefail via defaults.run.shell: bash -euo pipefail {0} on both jobs
  • PREV_TAG format regex check before parsing
  • Inline comment explaining why GitHub App token (vs default GITHUB_TOKEN) is required
  • Updated "safety-net" comment to reflect current state (ruleset is follow-up)

Hotfix support (new capability)

create-release.yaml now auto-detects from the dispatched branch:

  • main: uses gh api releases/latest → bumps from main line → marked as latest
  • v0.134.4-hotfix (matches ^v[0-9]+\.[0-9]+\.[0-9]+-hotfix$): extracts line 0.134, finds latest v0.134.x tag, bumps patch only, explicitly --latest=false — matches the convention in the Hotfix Deployment Guide
  • Other branches rejected

This preserves the existing hotfix workflow without requiring the manual release page (which the planned tag protection ruleset would block).

Verification

  • YAML syntax: ✓
  • Pre-commit hooks: ✓ all passed
  • Jaseem C1 (jq null) verified via shell: echo '[{"tagName":"v1"}]' | jq -r '.[1].tagName' → literal "null" (4 chars)
  • Jaseem C2 (sort -V -C equals) verified: printf '%s\n%s\n' "0.163.0" "0.163.0" | sort -V -C; echo $?0 (accepted)
  • Flow traced for: normal release, hotfix, new minor bump, workflow_dispatch, pre-release

Ready for re-review.

@github-actions
Copy link
Copy Markdown
Contributor

Test Results

Summary
  • Runner Tests: 11 passed, 0 failed (11 total)
  • SDK1 Tests: 196 passed, 0 failed (196 total)

Runner Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_logs}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup\_skip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_client\_init}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config\_without\_mount}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_run\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_for\_sidecar}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_sidecar\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{11}}$$ $$\textcolor{#23d18b}{\tt{11}}$$
SDK1 Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestPatchedEmbeddingSyncTimeoutForwarding.test\_timeout\_passed\_to\_client\_post}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestPatchedEmbeddingSyncTimeoutForwarding.test\_none\_timeout\_passed\_to\_client\_post}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestPatchedEmbeddingSyncTimeoutForwarding.test\_httpx\_timeout\_object\_forwarded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestMonkeyPatchApplied.test\_cohere\_handler\_patched}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestMonkeyPatchApplied.test\_bedrock\_handler\_patched}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestMonkeyPatchApplied.test\_patch\_module\_loaded\_via\_embedding\_import}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_round\_trip\_serialization}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_json\_serializable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_enum\_values\_normalized}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_string\_values\_accepted}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_auto\_generates\_request\_id}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_explicit\_request\_id\_preserved}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_optional\_organization\_id}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_empty\_executor\_params\_default}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_complex\_executor\_params}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_validation\_rejects\_empty\_required\_fields}}$$ $$\textcolor{#23d18b}{\tt{4}}$$ $$\textcolor{#23d18b}{\tt{4}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_all\_operations\_accepted}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_from\_dict\_missing\_optional\_fields}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_success\_round\_trip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_failure\_round\_trip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_json\_serializable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_failure\_requires\_error\_message}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_success\_allows\_no\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_success\_rejects\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_failure\_factory}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_failure\_factory\_no\_metadata}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_error\_none\_in\_success\_dict}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_error\_in\_failure\_dict}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_default\_empty\_dicts}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_from\_dict\_missing\_optional\_fields}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_response\_contract\_extract}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_response\_contract\_index}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_response\_contract\_answer\_prompt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestBaseExecutor.test\_cannot\_instantiate\_abstract}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestBaseExecutor.test\_concrete\_subclass\_works}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestBaseExecutor.test\_execute\_returns\_result}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_register\_and\_get}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_get\_returns\_fresh\_instance}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_register\_as\_decorator}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_list\_executors}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_list\_executors\_empty}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_get\_unknown\_raises\_key\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_get\_unknown\_lists\_available}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_duplicate\_name\_raises\_value\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_register\_non\_subclass\_raises\_type\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_register\_non\_class\_raises\_type\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_clear}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_execute\_through\_registry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionOrchestrator.test\_dispatches\_to\_correct\_executor}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionOrchestrator.test\_unknown\_executor\_returns\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionOrchestrator.test\_executor\_exception\_returns\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionOrchestrator.test\_exception\_result\_has\_elapsed\_metadata}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionOrchestrator.test\_successful\_result\_passed\_through}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionOrchestrator.test\_executor\_returning\_failure\_is\_not\_wrapped}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_sends\_task\_and\_returns\_result}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_uses\_default\_timeout}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_timeout\_from\_env}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_explicit\_timeout\_overrides\_env}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_timeout\_returns\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_generic\_exception\_returns\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_async\_returns\_task\_id}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_no\_app\_raises\_value\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_async\_no\_app\_raises\_value\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_failure\_result\_from\_executor}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_context\_serialized\_correctly}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_sends\_link\_and\_link\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_success\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_error\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_no\_callbacks}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_returns\_async\_result}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_no\_app\_raises\_value\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_context\_serialized}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_custom\_task\_id}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_no\_task\_id\_omits\_kwarg}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_platform\_api\_key\_returned}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_platform\_api\_key\_missing\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_other\_env\_var\_from\_environ}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_missing\_env\_var\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_empty\_env\_var\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_stream\_log\_routes\_to\_logging}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_stream\_log\_respects\_level}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_stream\_error\_and\_exit\_raises\_sdk\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_stream\_error\_and\_exit\_wraps\_original}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_model\_prefixes\_when\_missing}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_model\_does\_not\_double\_prefix}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_model\_blank\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_thinking\_disabled\_by\_default}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_excludes\_control\_fields\_from\_model}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_thinking\_enabled\_with\_budget}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_thinking\_overrides\_user\_temperature}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_thinking\_enabled\_without\_budget\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_thinking\_budget\_tokens\_invalid\_type\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_thinking\_budget\_tokens\_too\_small\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_preserves\_existing\_thinking\_config}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_does\_not\_mutate\_input}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_thinking\_controls\_not\_pydantic\_fields}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_api\_key\_is\_required}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_adapter\_identity}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_schema\_required\_fields}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_schema\_enable\_thinking\_default\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_schema\_budget\_tokens\_conditional}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatFromLlm.test\_from\_llm\_reuses\_llm\_instance}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatFromLlm.test\_from\_llm\_returns\_llmcompat\_instance}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatFromLlm.test\_from\_llm\_sets\_model\_name}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatFromLlm.test\_from\_llm\_does\_not\_call\_init}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_complete\_delegates\_to\_llm}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_chat\_delegates\_to\_llm\_complete}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_chat\_forwards\_kwargs\_to\_llm}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_complete\_forwards\_kwargs\_to\_llm}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_acomplete\_delegates\_to\_llm}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_achat\_delegates\_to\_llm\_acomplete}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_stream\_chat\_not\_implemented}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_stream\_complete\_not\_implemented}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_astream\_chat\_not\_implemented}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_astream\_complete\_not\_implemented}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_metadata\_returns\_emulated\_type}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_get\_model\_name\_delegates}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_get\_metrics\_delegates}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_test\_connection\_delegates}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestEmulatedTypes.test\_message\_role\_values}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestEmulatedTypes.test\_chat\_message\_defaults}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestEmulatedTypes.test\_chat\_response\_message\_access}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestEmulatedTypes.test\_completion\_response\_text}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestEmulatedTypes.test\_llm\_metadata\_defaults}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_single\_user\_message}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_none\_content\_becomes\_empty\_string}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_preserves\_all\_messages}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_multi\_turn\_conversation}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_empty\_messages\_returns\_empty\_string}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_string\_role\_fallback}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_non\_retryable\_http\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retryable\_http\_errors}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_post\_method\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_logging}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_retry\_on\_errors}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_wrapper\_methods\_retry}}$$ $$\textcolor{#23d18b}{\tt{4}}$$ $$\textcolor{#23d18b}{\tt{4}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_connection\_error\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_timeout\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_non\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_without\_response}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_non\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_other\_exception\_not\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_without\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_successful\_call\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_after\_transient\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_max\_retries\_exceeded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_with\_custom\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_no\_retry\_with\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_exception\_not\_in\_tuple\_not\_retried}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_default\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_environment\_variable\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_max\_retries}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_base\_delay}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_multiplier}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_jitter\_values}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_exceptions\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_predicate\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_both\_exceptions\_and\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_exceptions\_match\_but\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_platform\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_prompt\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_platform\_service\_decorator\_retries\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_prompt\_service\_decorator\_retries\_on\_timeout}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_warning\_logged\_on\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_info\_logged\_on\_success\_after\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_exception\_logged\_on\_giving\_up}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{196}}$$ $$\textcolor{#23d18b}{\tt{196}}$$

@sonarqubecloud
Copy link
Copy Markdown

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

Tip

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

🧩 Analysis chain

I'll review the updated workflow files to verify all the fixes described.


🏁 Script executed:

#!/bin/bash
cat .github/workflows/create-release.yaml
echo "---FILE_BOUNDARY---"
cat .github/workflows/production-build.yaml

Length of output: 26105


@muhammad-ali-e: Excellent and thorough followup — I've reviewed the full content of both updated workflow files against every item in your resolution table. Here's what I found:


✅ All critical and correctness fixes verified

Every item in your resolution table is correctly implemented in the code:

Fix Verified
// empty on all jq lookups ✅ Both files
Explicit $PREV_VER == $NEW_VER equal guard ✅ Both files
gh api releases/latest for main; same-line lookup for hotfixes ✅ Both files
Major bump no longer unconditionally blocked validate-tag uses same-line comparison only
if: success() && ... on Summary step ✅ Confirmed
Branch restriction to main / vX.Y.Z-hotfix ✅ Confirmed
env: block on Set version tag ✅ Confirmed
build-summary gated on validate-tag.result == 'success' ✅ Confirmed
workflow_dispatch no longer skips increment check ✅ No github.event_name == 'release' guard in validate-tag
Tag collision check via gh release view ✅ New step present
Default case in bump-type switch ✅ Clear error message
set -euo pipefail via defaults.run.shell ✅ Both jobs
PREV/LATEST tag format regex check before parsing ✅ Both files
GitHub App token inline comment ✅ Present
Hotfix: --latest=false, same-line lookup, patch-only guard ✅ Full implementation confirmed

✅ Action versions confirmed current

The action versions in production-build.yaml are confirmed as valid: actions/checkout@v6 is the current latest on the official actions/checkout repo. The archive: false feature requires actions/upload-artifact@v7, and using v8 of actions/download-artifact is the corresponding pairing for that feature.


Minor residual observations (non-blocking)

  1. gh release list ordering for hotfix same-line lookup in validate-tag: .[0] in the jq filter relies on creation-date ordering from gh release list. For a hotfix line where patches are always cut sequentially this is fine in practice, but if two hotfix patches on the same line were ever created out of chronological order, the sort -V -C comparison itself would still catch any regression. Net effect: no real risk.

  2. ${{ github.actor }} direct interpolation in Summary heredoc: github.actor is alphanumeric-only and GitHub-controlled, so this is very low risk. The env: pattern would be strictly cleaner but this is cosmetic.

The implementation is solid. LGTM — happy to approve when you're ready.

[approve]

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
.github/workflows/production-build.yaml (1)

140-167: ⚠️ Potential issue | 🟠 Major

Don't publish latest for GitHub pre-releases.

The release event triggers for both full releases and pre-releases. Currently, this block assigns latest for every release event, which means a GitHub pre-release will overwrite stable latest images even though the release is explicitly marked as prerelease.

Gate latest on github.event.release.prerelease != true while keeping set_as_latest as the explicit manual override.

Suggested fix
       - name: Set image tags
         id: tags
+        env:
+          RELEASE_PRERELEASE: ${{ github.event.release.prerelease }}
         run: |
           # Check if service exists in the config
           echo "Checking if service ${{ matrix.service_name }} exists in docker-compose.build.yaml"
           if ! grep -q "^  ${{ matrix.service_name }}:" ./docker/docker-compose.build.yaml; then
             echo "Service ${{ matrix.service_name }} not found in docker-compose.build.yaml" && exit 1
@@
-          if [ "${{ github.event_name }}" = "release" ] || [ "${{ github.event.inputs.set_as_latest }}" = "true" ]; then
+          if { [ "${{ github.event_name }}" = "release" ] && [ "${RELEASE_PRERELEASE}" != "true" ]; } || [ "${{ github.event.inputs.set_as_latest }}" = "true" ]; then
             echo "LATEST_IMAGE_TAG=unstract/${{ matrix.service_name }}:latest" >> $GITHUB_ENV
           else
             echo "LATEST_IMAGE_TAG=" >> $GITHUB_ENV
           fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/production-build.yaml around lines 140 - 167, The current
"Set image tags" step unconditionally treats any release event as a stable
release and sets LATEST_IMAGE_TAG; change the conditional so LATEST_IMAGE_TAG is
set only when the event is a non-prerelease release OR when the manual input
set_as_latest is true. Update the if in the run block that sets LATEST_IMAGE_TAG
(inside the "Set image tags" step referencing matrix.service_name and
env.DOCKER_VERSION_TAG) to require github.event_name == "release" AND
github.event.release.prerelease != true, or to allow the override when
github.event.inputs.set_as_latest == "true" (i.e., ((release && prerelease !=
true) || set_as_latest == true)).
♻️ Duplicate comments (1)
.github/workflows/production-build.yaml (1)

64-67: ⚠️ Potential issue | 🟠 Major

Include pre-releases when selecting the same-line baseline.

Line 65 still excludes pre-releases from the PREV_TAG lookup. That means a prerelease-only line can fall into the "No prior release" branch and skip monotonicity entirely, so a regressing prerelease on that line would still pass this guard. Compare against the latest same-line release regardless of prerelease status, or branch on github.event.release.prerelease when choosing the baseline.

Suggested fix
-          PREV_TAG=$(gh release list --repo "${{ github.repository }}" \
-            --limit 100 --exclude-drafts --exclude-pre-releases \
+          PREV_TAG=$(gh release list --repo "${{ github.repository }}" \
+            --limit 100 --exclude-drafts \
             --json tagName \
             --jq "[.[] | select(.tagName != \"$NEW_TAG\" and (.tagName | startswith(\"v${NEW_MAJOR_MINOR}.\")))] | .[0].tagName // empty")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/production-build.yaml around lines 64 - 67, The PREV_TAG
lookup excludes prereleases (the --exclude-pre-releases flag in the gh release
list call that sets PREV_TAG), which causes prerelease-only lines to be treated
as "No prior release"; update the logic so PREV_TAG compares against the latest
same-line tag regardless of prerelease status by removing the
--exclude-pre-releases filter from the gh release list call that uses NEW_TAG
and NEW_MAJOR_MINOR, or alternatively branch on github.event.release.prerelease
and choose the gh release list invocation accordingly (include prereleases when
github.event.release.prerelease is true, keep excluding them otherwise) so the
same-line baseline always considers prereleases when appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In @.github/workflows/production-build.yaml:
- Around line 140-167: The current "Set image tags" step unconditionally treats
any release event as a stable release and sets LATEST_IMAGE_TAG; change the
conditional so LATEST_IMAGE_TAG is set only when the event is a non-prerelease
release OR when the manual input set_as_latest is true. Update the if in the run
block that sets LATEST_IMAGE_TAG (inside the "Set image tags" step referencing
matrix.service_name and env.DOCKER_VERSION_TAG) to require github.event_name ==
"release" AND github.event.release.prerelease != true, or to allow the override
when github.event.inputs.set_as_latest == "true" (i.e., ((release && prerelease
!= true) || set_as_latest == true)).

---

Duplicate comments:
In @.github/workflows/production-build.yaml:
- Around line 64-67: The PREV_TAG lookup excludes prereleases (the
--exclude-pre-releases flag in the gh release list call that sets PREV_TAG),
which causes prerelease-only lines to be treated as "No prior release"; update
the logic so PREV_TAG compares against the latest same-line tag regardless of
prerelease status by removing the --exclude-pre-releases filter from the gh
release list call that uses NEW_TAG and NEW_MAJOR_MINOR, or alternatively branch
on github.event.release.prerelease and choose the gh release list invocation
accordingly (include prereleases when github.event.release.prerelease is true,
keep excluding them otherwise) so the same-line baseline always considers
prereleases when appropriate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3adb00c9-e67a-4f6a-866f-0c02e0e53998

📥 Commits

Reviewing files that changed from the base of the PR and between cb60f51 and a274f13.

📒 Files selected for processing (2)
  • .github/workflows/create-release.yaml
  • .github/workflows/production-build.yaml
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/create-release.yaml

@jaseemjaskp jaseemjaskp self-requested a review April 16, 2026 07:10
Copy link
Copy Markdown
Contributor

@jaseemjaskp jaseemjaskp left a comment

Choose a reason for hiding this comment

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

LGTM

@jaseemjaskp jaseemjaskp merged commit 275afec into main Apr 16, 2026
8 checks passed
@jaseemjaskp jaseemjaskp deleted the misc/UN-3408-MISC_release_workflow_validation branch April 16, 2026 11:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants