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
2 changes: 1 addition & 1 deletion .github/workflows/claude-pr-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ jobs:
# path; this bearer-token path skips that infra and is the same one
# the Claude CLI uses directly (exercised end-to-end by the
# live-tests BedrockRoute step).
uses: anthropics/claude-code-action@09f876c77e9d2d627cdf3e2f83d4d7b75106807e # v1.0.134
uses: anthropics/claude-code-action@37b464ce72700f7b2c5ff8d2db7fa7b15df792f5 # v1.0.169
env:
AWS_BEARER_TOKEN_BEDROCK: ${{ secrets.AWS_BEARER_TOKEN_BEDROCK }}
AWS_REGION: ${{ secrets.AWS_REGION }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ permissions:
jobs:
analyze:
name: Analyze Python code
runs-on: uipath-ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
security-events: write
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ env:
jobs:
publish:
name: Build and push to GHCR
runs-on: uipath-ubuntu-latest
runs-on: ubuntu-latest
# Skip semantic-release's own commit so we don't double-build on the
# version-bump push.
if: "!contains(github.event.head_commit.message, 'chore(release):')"
Expand Down
26 changes: 19 additions & 7 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ env:
jobs:
quality-gate:
name: Quality Gate (Format, Lint, Type, Test, Security)
runs-on: uipath-ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 10

# Shared env for all steps: safe-chain min-age exclusions and a dummy
Expand All @@ -51,10 +51,14 @@ jobs:
- name: Cache dependencies
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
# ``.venv`` is deliberately NOT cached: its interpreter symlinks
# point at the exact hosted-Python patch, so a patch bump (e.g.
# 3.13.13 -> 3.13.14) leaves a dangling ``.venv/bin/python`` and
# every ``.venv/bin/*`` fails with exit 127 (cannot execute).
# ``uv sync --frozen`` rehydrates ``.venv`` from the cached wheels.
path: |
~/.cache/uv
~/.cache/pip
.venv
.pytest_cache
.ruff_cache
key: ${{ runner.os }}-py3.13-${{ hashFiles('pyproject.toml', 'uv.lock') }}
Expand Down Expand Up @@ -159,7 +163,7 @@ jobs:
# registry validates, and the uipath-specific code paths fail with a
# clear hint instead of an import error.
name: No-Extra Install (uipath optional)
runs-on: uipath-ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout code
Expand Down Expand Up @@ -322,7 +326,7 @@ jobs:

e2e-smoke:
name: E2E Smoke Tests (Real API)
runs-on: uipath-ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 10
# Skip on fork PRs where secrets aren't available
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
Expand Down Expand Up @@ -379,10 +383,12 @@ jobs:
- name: Cache dependencies
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
# ``.venv`` excluded: a hosted-Python patch bump orphans its
# interpreter symlink (dangling ``.venv/bin/python`` -> exit 127).
# ``uv sync --frozen`` rehydrates it from the cached wheels.
path: |
~/.cache/uv
~/.cache/pip
.venv
key: ${{ runner.os }}-py3.13-e2e-${{ hashFiles('pyproject.toml', 'uv.lock') }}
restore-keys: |
${{ runner.os }}-py3.13-e2e-
Expand Down Expand Up @@ -544,10 +550,12 @@ jobs:
- name: Cache dependencies
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
# ``.venv`` excluded: a hosted-Python patch bump orphans its
# interpreter symlink (dangling ``.venv/bin/python`` -> exit 127).
# ``uv sync --frozen`` rehydrates it from the cached wheels.
path: |
~/.cache/uv
~/.cache/pip
.venv
key: ${{ runner.os }}-py3.13-live-${{ hashFiles('pyproject.toml', 'uv.lock') }}
restore-keys: |
${{ runner.os }}-py3.13-live-
Expand Down Expand Up @@ -714,8 +722,12 @@ jobs:
- name: Run Codex live tests
run: |
mkdir -p tmp
# Run serially: `-n0` overrides the global `-n auto` (addopts).
# Parallel xdist workers share ~/.codex and race the Codex SQLite
# state migration (`duplicate column name: thread_id`); serial init
# migrates the fresh DB exactly once.
.venv/bin/pytest tests/test_codex_agent_live.py \
-m live -v --tb=short --strict-markers -ra \
-m live -n0 -v --tb=short --strict-markers -ra \
--junit-xml=tmp/junit-codex-live.xml

- name: Assert Codex live tests actually ran (not silently skipped)
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ permissions:
jobs:
publish-dev:
name: Publish dev wheel for PR
runs-on: uipath-ubuntu-latest
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'build:dev')
timeout-minutes: 15
env:
Expand Down
98 changes: 98 additions & 0 deletions .github/workflows/publish-testpypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: Publish to TestPyPI (dry run)

# Manually-triggered dry run of the public-PyPI publish path against
# https://test.pypi.org. Use this to validate the build + OIDC Trusted
# Publishing flow BEFORE cutting a real release (release.yml -> pypi.org).
#
# This does NOT bump the version, tag, or touch main / the Azure feed. It
# stamps a throwaway PEP 440 dev version (`<current>.dev<run#>`) so repeated
# runs never collide with an already-uploaded TestPyPI file, builds, and
# publishes via OIDC (no stored token).
#
# One-time setup (mirrors the real PyPI publisher, on the TEST instance):
# test.pypi.org -> Account settings -> Publishing -> add a pending publisher:
# Project name: coder-eval
# Owner: UiPath
# Repository: coder_eval
# Workflow name: publish-testpypi.yml
# Environment: testpypi
#
# Install the dry-run build to smoke-test it:
# uv pip install --index-url https://test.pypi.org/simple/ \
# --extra-index-url https://pypi.org/simple/ coder-eval

on:
workflow_dispatch:

concurrency:
group: publish-testpypi
cancel-in-progress: false

permissions:
contents: read

jobs:
publish-testpypi:
name: Build and publish to TestPyPI
runs-on: ubuntu-latest
timeout-minutes: 10
environment:
name: testpypi
url: https://test.pypi.org/project/coder-eval/
permissions:
# OIDC token minting for Trusted Publishing; no long-lived credentials.
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Python 3.13
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.13"

- name: Install uv
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4.2.0
with:
enable-cache: true

- name: Stamp a throwaway dev version
env:
RUN_NUMBER: ${{ github.run_number }}
run: |
set -euo pipefail
CURRENT_VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
# PEP 440 dev release: sorts before the base release, unique per run.
DEV_VERSION="${CURRENT_VERSION}.dev${RUN_NUMBER}"
python3 - <<PY
import re, pathlib
# pyproject.toml is the canonical source; __init__ is kept in sync.
for path, pat in (
("pyproject.toml", r'(?m)^version\s*=\s*".+?"$'),
("src/coder_eval/__init__.py", r'(?m)^__version__\s*=\s*".+?"$'),
):
p = pathlib.Path(path)
key = "version" if path.endswith("toml") else "__version__"
# re.subn (not re.sub) so a version-line format drift aborts loudly
# instead of silently no-op'ing (rc=0) and publishing a stale/base
# version that then collides on TestPyPI (masked by skip-existing).
new, n = re.subn(pat, f'{key} = "${DEV_VERSION}"', p.read_text(), count=1)
if n != 1:
raise SystemExit(f"version pattern did not match {path} (matched {n}); refusing to publish a stale/colliding artifact")
p.write_text(new)
PY
echo "Stamped dev version: ${DEV_VERSION}"

- name: Build wheel + sdist
run: uv build

- name: Publish to TestPyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
# Trusted Publisher is configured on test.pypi.org for this repo +
# workflow (publish-testpypi.yml) + environment (testpypi); no
# password needed.
repository-url: https://test.pypi.org/legacy/
packages-dir: dist/
# A re-run at the same dev version is a no-op rather than a hard fail.
skip-existing: true
66 changes: 58 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,20 @@ permissions:
jobs:
release:
name: Bump version and publish
runs-on: uipath-ubuntu-latest
# GitHub-hosted so cutting a release does not depend on the self-hosted
# `uipath-ubuntu-latest` pool. semantic-release, uv, twine, and the docker
# buildx -> GHCR push all run fine here.
runs-on: ubuntu-latest
timeout-minutes: 15
outputs:
# Exposed so the downstream publish-pypi job can gate on a real release
# having been cut (empty => no version bumped => skip PyPI publish).
version: ${{ steps.release.outputs.version }}
env:
# uipath-ubuntu-latest runners enforce a minimum package-age safe-chain
# check on uv installs; match the exclusions used by pr-checks.yml so
# parity is maintained if/when codex deps flow through the release path.
# The self-hosted `uipath-ubuntu-latest` runners enforce a minimum
# package-age safe-chain check on uv installs; on GitHub-hosted runners
# this is a no-op. Kept (matching pr-checks.yml) so parity is preserved
# if the job ever moves back to the self-hosted pool.
SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS: "openai-codex-cli-bin,openai-codex"

steps:
Expand All @@ -71,7 +79,7 @@ jobs:
python-version: "3.13"

- name: Install uv
uses: astral-sh/setup-uv@v4
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4.2.0
with:
enable-cache: true

Expand Down Expand Up @@ -118,6 +126,17 @@ jobs:
if: steps.release.outputs.version != ''
run: uv build

# Hand the exact built artifacts to the publish-pypi job. Publishing to
# public PyPI runs in its own environment-gated job (OIDC), so it must
# consume these files rather than rebuild them.
- name: Upload dist for PyPI publish
if: steps.release.outputs.version != ''
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: release-dist
path: dist/
if-no-files-found: error

- name: Publish to coder_eval feed
if: steps.release.outputs.version != ''
env:
Expand All @@ -142,19 +161,19 @@ jobs:

- name: Set up Docker Buildx
if: steps.release.outputs.version != ''
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0

- name: Log in to GHCR
if: steps.release.outputs.version != ''
uses: docker/login-action@v3
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push versioned agent image
if: steps.release.outputs.version != ''
uses: docker/build-push-action@v6
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
with:
context: .
file: docker/Dockerfile
Expand All @@ -170,3 +189,34 @@ jobs:
# Read the shared buildcache docker-publish.yml writes; don't write it
# back from here, to avoid two concurrent writers racing the cache tag.
cache-from: type=registry,ref=ghcr.io/${{ steps.img.outputs.owner_lc }}/coder-eval-agent:buildcache

# Publish the SAME wheel+sdist to public PyPI (pkgs live alongside the private
# Azure Artifacts feed, which the release job above still populates). This runs
# as its own job so OIDC Trusted Publishing is scoped to a dedicated,
# environment-gated context on GitHub-hosted runners -- no PyPI token/secret is
# stored. Gated on the release job having actually cut a version.
publish-pypi:
name: Publish to PyPI
needs: release
if: needs.release.outputs.version != ''
runs-on: ubuntu-latest
timeout-minutes: 10
environment:
name: pypi
url: https://pypi.org/project/coder-eval/${{ needs.release.outputs.version }}/
permissions:
# OIDC token minting for Trusted Publishing; no long-lived credentials.
id-token: write
steps:
- name: Download built dist
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: release-dist
path: dist/

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
# Trusted Publisher is configured on pypi.org for this repo +
# workflow (release.yml) + environment (pypi); no password needed.
packages-dir: dist/
Loading