Skip to content

Commit 3a62e62

Browse files
OriNachumclaude
andauthored
fix: combine release and publish workflows for reliable PyPI deploys (#51)
* fix: combine release and publish workflows for reliable PyPI deploys The auto-release workflow created GitHub Releases using GITHUB_TOKEN, but GitHub's security restriction prevents GITHUB_TOKEN events from triggering other workflows. This meant publish.yml never fired from automated releases — the fundamental reason PyPI deploys were broken. - Merge auto-release.yml logic into publish.yml (single workflow handles version-push trigger → release creation → build → PyPI publish) - Delete auto-release.yml (absorbed) and legacy release.yml (redundant with docker-publish.yml) - Fix __init__.py: import __version__ from version.py instead of hardcoding stale '0.1.1' - Bump version to 0.4.3 to trigger the new pipeline on merge Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: document release process in CLAUDE.md Add release workflow instructions: bump version.py, merge to main, publish.yml handles the rest. Also remove stale server.py reference (deleted in ccdc7aa). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review feedback on publish workflow - Add trailing newline to __init__.py (flake8 W292) - Make workflow idempotent: only build/publish when a new release is created or trigger is a manual release event (output gate via create-release.outputs.created) - Least-privilege permissions: top-level contents:read, contents:write only on create-release job - Remove unused GH_TOKEN env from stale tag deletion step (git push uses credentials from actions/checkout) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ccdc7aa commit 3a62e62

File tree

6 files changed

+78
-134
lines changed

6 files changed

+78
-134
lines changed

.github/workflows/auto-release.yml

Lines changed: 0 additions & 56 deletions
This file was deleted.

.github/workflows/publish.yml

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,80 @@
1-
name: Upload Python Package
1+
name: Publish to PyPI
22

33
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "src/open_responses_server/version.py"
48
release:
59
types: [published]
10+
workflow_dispatch:
611

712
permissions:
813
contents: read
9-
id-token: write # Mandatory for trusted publishing
14+
id-token: write
15+
16+
concurrency:
17+
group: pypi-publish
18+
cancel-in-progress: false
1019

1120
jobs:
21+
create-release:
22+
if: github.event_name != 'release'
23+
runs-on: ubuntu-latest
24+
permissions:
25+
contents: write
26+
outputs:
27+
created: ${{ steps.create.outputs.created }}
28+
29+
steps:
30+
- uses: actions/checkout@v4
31+
with:
32+
fetch-depth: 0
33+
34+
- name: Extract version
35+
id: version
36+
run: |
37+
VERSION=$(python3 -c "import re; print(re.search(r'__version__\s*=\s*\"([^\"]+)\"', open('src/open_responses_server/version.py').read()).group(1))")
38+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
39+
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"
40+
41+
- name: Check if release already exists
42+
id: check
43+
run: |
44+
if gh release view "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
45+
echo "exists=true" >> "$GITHUB_OUTPUT"
46+
else
47+
echo "exists=false" >> "$GITHUB_OUTPUT"
48+
fi
49+
env:
50+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51+
52+
- name: Delete stale tag if exists
53+
if: steps.check.outputs.exists == 'false'
54+
run: |
55+
if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
56+
git push origin ":refs/tags/${{ steps.version.outputs.tag }}" || true
57+
fi
58+
59+
- name: Create GitHub Release
60+
id: create
61+
if: steps.check.outputs.exists == 'false'
62+
run: |
63+
gh release create "${{ steps.version.outputs.tag }}" \
64+
--title "Release ${{ steps.version.outputs.tag }}" \
65+
--generate-notes \
66+
--target "${{ github.sha }}"
67+
echo "created=true" >> "$GITHUB_OUTPUT"
68+
env:
69+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
70+
1271
release-build:
72+
needs: [create-release]
73+
if: >-
74+
always() && (
75+
needs.create-release.result == 'skipped' ||
76+
needs.create-release.outputs.created == 'true'
77+
)
1378
runs-on: ubuntu-latest
1479

1580
steps:

.github/workflows/release.yml

Lines changed: 0 additions & 71 deletions
This file was deleted.

CLAUDE.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ api_controller.py -- FastAPI app with route definitions, CORS, startup/shutdown
7070
```
7171

7272
**Key modules:**
73-
- `api_controller.py` - Route definitions. `server.py` is a legacy duplicate; `server_entrypoint.py` is the uvicorn entry point that imports from `api_controller`.
73+
- `api_controller.py` - Route definitions. `server_entrypoint.py` is the uvicorn entry point that imports from `api_controller`.
7474
- `responses_service.py` - Converts Responses API requests to Chat Completions format (`convert_responses_to_chat_completions`), processes streaming Chat Completions responses back into Responses API SSE events (`process_chat_completions_stream`). Maintains in-memory `conversation_history` keyed by `previous_response_id`.
7575
- `chat_completions_service.py` - Handles `/v1/chat/completions` with MCP tool injection. Implements a tool-call loop (up to `MAX_TOOL_CALL_ITERATIONS`) for both streaming and non-streaming modes.
7676
- `common/mcp_manager.py` - `MCPManager` singleton manages MCP server lifecycle (stdio-based), tool discovery/caching with periodic refresh, and tool execution. `MCPServer` wraps individual server sessions.
@@ -84,9 +84,15 @@ api_controller.py -- FastAPI app with route definitions, CORS, startup/shutdown
8484

8585
All config is via environment variables (see `common/config.py`). The CLI command `otc configure` writes a `.env` file interactively. MCP servers are configured in a JSON file pointed to by `MCP_SERVERS_CONFIG_PATH` (default: `src/open_responses_server/servers_config.json`).
8686

87-
## Version
87+
## Version & Releasing
8888

89-
Version lives in `src/open_responses_server/version.py` as `__version__` and is read dynamically by setuptools.
89+
Version lives in `src/open_responses_server/version.py` as `__version__` — the single source of truth. Both `pyproject.toml` (via setuptools dynamic) and `__init__.py` read from it.
90+
91+
**To release a new version:** bump `__version__` in `version.py`, merge to main. The `publish.yml` workflow handles everything automatically:
92+
1. Detects `version.py` changed on main
93+
2. Creates a GitHub Release (v{version})
94+
3. Builds with `uv build` + `twine check`
95+
4. Publishes to PyPI via trusted publishing (OIDC)
9096

9197
## CLI Entry Point
9298

src/open_responses_server/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
OpenAI Responses API Server - Adapter for converting between API formats.
33
"""
44

5-
__version__ = '0.1.1'
5+
from open_responses_server.version import __version__
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/usr/bin/env python3
22
"""Version information."""
33

4-
__version__ = "0.4.2"
4+
__version__ = "0.4.3"

0 commit comments

Comments
 (0)