Skip to content

Commit e9e7c86

Browse files
CSResselnori-agent
andcommitted
ci: Add automated upstream sync workflow
Adds GitHub Actions workflow that: - Runs daily at 9 AM UTC to check for new upstream stable releases - Detects latest stable tag (X.Y.Z only, no alpha/beta) - Updates fork/upstream-sync branch to track release - Creates sync/upstream-vX.Y.Z branch from the tag - Opens draft PR against dev with merge instructions Supports manual trigger with optional tag and dry-run mode. Idempotent - skips if sync branch already exists. Also adds nori-releases.md documenting the branching strategy and sync workflow. 🤖 Generated with [Nori](https://nori.ai) Co-Authored-By: Nori <noreply@tilework.tech>
1 parent 96cafb4 commit e9e7c86

5 files changed

Lines changed: 343 additions & 10 deletions

File tree

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
# Automatically sync stable upstream releases into dev branch.
2+
# Creates a sync branch and draft PR for each new stable release.
3+
#
4+
# Manual trigger: gh workflow run upstream-sync.yml
5+
# With specific tag: gh workflow run upstream-sync.yml -f tag=rust-v0.63.0
6+
# Dry run: gh workflow run upstream-sync.yml -f dry_run=true
7+
8+
name: upstream-sync
9+
10+
on:
11+
schedule:
12+
# Daily at 9 AM UTC (reasonable time for review in US timezones)
13+
- cron: '0 9 * * *'
14+
workflow_dispatch:
15+
inputs:
16+
tag:
17+
description: 'Specific tag to sync (optional, defaults to latest stable)'
18+
required: false
19+
type: string
20+
dry_run:
21+
description: 'Dry run mode (log actions without creating branches/PRs)'
22+
required: false
23+
type: boolean
24+
default: false
25+
26+
env:
27+
UPSTREAM_REMOTE: https://github.com/openai/codex.git
28+
29+
jobs:
30+
sync:
31+
runs-on: ubuntu-latest
32+
permissions:
33+
contents: write
34+
pull-requests: write
35+
steps:
36+
- name: Checkout repository
37+
uses: actions/checkout@v4
38+
with:
39+
fetch-depth: 0
40+
token: ${{ secrets.GITHUB_TOKEN }}
41+
42+
- name: Configure git
43+
run: |
44+
git config user.name "github-actions[bot]"
45+
git config user.email "github-actions[bot]@users.noreply.github.com"
46+
47+
- name: Add upstream remote and fetch tags
48+
run: |
49+
git remote add upstream "$UPSTREAM_REMOTE" || git remote set-url upstream "$UPSTREAM_REMOTE"
50+
git fetch upstream --tags --force
51+
52+
- name: Determine target tag
53+
id: tag
54+
run: |
55+
set -euo pipefail
56+
57+
if [[ -n "${{ inputs.tag }}" ]]; then
58+
target_tag="${{ inputs.tag }}"
59+
echo "Using manually specified tag: $target_tag"
60+
else
61+
# Find latest stable tag (X.Y.Z only, no alpha/beta/rc)
62+
# Pattern: rust-v followed by semver without prerelease suffix
63+
target_tag=$(git tag -l 'rust-v*' \
64+
| grep -E '^rust-v[0-9]+\.[0-9]+\.[0-9]+$' \
65+
| sort -V \
66+
| tail -1)
67+
echo "Detected latest stable tag: $target_tag"
68+
fi
69+
70+
if [[ -z "$target_tag" ]]; then
71+
echo "::error::No stable upstream tag found"
72+
exit 1
73+
fi
74+
75+
# Validate tag exists
76+
if ! git rev-parse "$target_tag" >/dev/null 2>&1; then
77+
echo "::error::Tag $target_tag does not exist"
78+
exit 1
79+
fi
80+
81+
# Extract version for branch naming (rust-v0.63.0 -> v0.63.0)
82+
version="${target_tag#rust-}"
83+
sync_branch="sync/upstream-${version}"
84+
85+
echo "target_tag=$target_tag" >> "$GITHUB_OUTPUT"
86+
echo "version=$version" >> "$GITHUB_OUTPUT"
87+
echo "sync_branch=$sync_branch" >> "$GITHUB_OUTPUT"
88+
89+
- name: Check if sync branch already exists
90+
id: check
91+
run: |
92+
set -euo pipefail
93+
94+
sync_branch="${{ steps.tag.outputs.sync_branch }}"
95+
96+
# Check if branch exists on origin
97+
if git ls-remote --heads origin "$sync_branch" | grep -q "$sync_branch"; then
98+
echo "::notice::Sync branch $sync_branch already exists, skipping"
99+
echo "exists=true" >> "$GITHUB_OUTPUT"
100+
else
101+
echo "Sync branch $sync_branch does not exist, will create"
102+
echo "exists=false" >> "$GITHUB_OUTPUT"
103+
fi
104+
105+
- name: Update fork/upstream-sync tracking branch
106+
if: steps.check.outputs.exists == 'false'
107+
run: |
108+
set -euo pipefail
109+
110+
target_tag="${{ steps.tag.outputs.target_tag }}"
111+
dry_run="${{ inputs.dry_run }}"
112+
113+
echo "Updating fork/upstream-sync to point to $target_tag"
114+
115+
if [[ "$dry_run" == "true" ]]; then
116+
echo "::notice::DRY RUN: Would update fork/upstream-sync to $target_tag"
117+
else
118+
# Create or update the tracking branch
119+
git branch -f fork/upstream-sync "$target_tag"
120+
git push origin fork/upstream-sync --force
121+
fi
122+
123+
- name: Create sync branch
124+
if: steps.check.outputs.exists == 'false'
125+
run: |
126+
set -euo pipefail
127+
128+
target_tag="${{ steps.tag.outputs.target_tag }}"
129+
sync_branch="${{ steps.tag.outputs.sync_branch }}"
130+
dry_run="${{ inputs.dry_run }}"
131+
132+
echo "Creating sync branch $sync_branch from $target_tag"
133+
134+
if [[ "$dry_run" == "true" ]]; then
135+
echo "::notice::DRY RUN: Would create branch $sync_branch from $target_tag"
136+
else
137+
git checkout -b "$sync_branch" "$target_tag"
138+
git push origin "$sync_branch"
139+
fi
140+
141+
- name: Create draft PR
142+
if: steps.check.outputs.exists == 'false'
143+
env:
144+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
145+
TARGET_TAG: ${{ steps.tag.outputs.target_tag }}
146+
SYNC_BRANCH: ${{ steps.tag.outputs.sync_branch }}
147+
DRY_RUN: ${{ inputs.dry_run }}
148+
run: |
149+
set -euo pipefail
150+
151+
# Count commits between dev and the tag for context
152+
commit_count=$(git rev-list --count origin/dev.."$TARGET_TAG" 2>/dev/null || echo "unknown")
153+
154+
pr_title="Sync upstream $TARGET_TAG"
155+
156+
# Build PR body using heredoc
157+
pr_body=$(cat <<EOF
158+
## Upstream Sync
159+
160+
This PR syncs changes from upstream release \`$TARGET_TAG\`.
161+
162+
### Summary
163+
164+
- **Upstream tag:** \`$TARGET_TAG\`
165+
- **Commits to merge:** ~$commit_count
166+
- **Release notes:** [GitHub Release](https://github.com/openai/codex/releases/tag/$TARGET_TAG)
167+
168+
### Merge Instructions
169+
170+
1. Review the changes for conflicts with our ACP fork work
171+
2. Resolve any merge conflicts:
172+
\`\`\`bash
173+
git checkout dev
174+
git merge $SYNC_BRANCH --no-ff
175+
# Resolve conflicts if any
176+
\`\`\`
177+
3. Run tests: \`cd codex-rs && cargo test\`
178+
4. Update snapshot tests if needed: \`cargo insta review\`
179+
5. Mark as ready for review when satisfied
180+
181+
### After Merge
182+
183+
- Delete the \`$SYNC_BRANCH\` branch
184+
- Consider tagging a new nori release if significant changes
185+
EOF
186+
)
187+
188+
if [[ "$DRY_RUN" == "true" ]]; then
189+
echo "::notice::DRY RUN: Would create PR with title: $pr_title"
190+
echo "PR body would be:"
191+
echo "$pr_body"
192+
else
193+
gh pr create \
194+
--base dev \
195+
--head "$SYNC_BRANCH" \
196+
--title "$pr_title" \
197+
--body "$pr_body" \
198+
--draft
199+
200+
echo "::notice::Created draft PR for $TARGET_TAG"
201+
fi
202+
203+
- name: Summary
204+
run: |
205+
echo "## Upstream Sync Summary" >> "$GITHUB_STEP_SUMMARY"
206+
echo "" >> "$GITHUB_STEP_SUMMARY"
207+
echo "- **Target tag:** ${{ steps.tag.outputs.target_tag }}" >> "$GITHUB_STEP_SUMMARY"
208+
echo "- **Sync branch:** ${{ steps.tag.outputs.sync_branch }}" >> "$GITHUB_STEP_SUMMARY"
209+
echo "- **Branch existed:** ${{ steps.check.outputs.exists }}" >> "$GITHUB_STEP_SUMMARY"
210+
echo "- **Dry run:** ${{ inputs.dry_run || 'false' }}" >> "$GITHUB_STEP_SUMMARY"

codex-rs/tui/config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
[projects."/home/clifford/Documents/source/codex"]
22
trust_level = "untrusted"
3+
4+
[projects."/home/clifford/Documents/source/nori/cli"]
5+
trust_level = "untrusted"

codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__blackbox_model_picker_open.snap

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ expression: terminal.backend()
77
" Select Model and Effort "
88
" Access legacy models by running codex -m <model_name> or in your config.toml "
99
" "
10-
"› 1. gpt-5.1-codex (current) Optimized for codex. "
11-
" 2. gpt-5.1-codex-mini Optimized for codex. Cheaper, faster, but less capable. "
12-
" 3. gpt-5.1 Broad world knowledge with strong general reasoning. "
10+
" 1. Mock ACP Agent Mock agent for testing purposes. "
11+
" 2. Gemini 2.0 Flash Thinking Google's experimental thinking model. "
12+
" 3. Claude Anthropic's Claude via Agent Context Protocol. "
13+
"› 4. gpt-5.1-codex (current) Optimized for codex. "
14+
" 5. gpt-5.1-codex-mini Optimized for codex. Cheaper, faster, but less capable. "
15+
" 6. gpt-5.1 Broad world knowledge with strong general reasoning. "
1316
" "
1417
" Press enter to select reasoning effort, or esc to dismiss. "
1518
" "
@@ -29,6 +32,3 @@ expression: terminal.backend()
2932
" "
3033
" "
3134
" "
32-
" "
33-
" "
34-
" "

codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__model_selection_popup.snap

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ expression: popup
55
Select Model and Effort
66
Access legacy models by running codex -m <model_name> or in your config.toml
77

8-
› 1. gpt-5.1-codex Optimized for codex.
9-
2. gpt-5.1-codex-mini Optimized for codex. Cheaper, faster, but less
10-
capable.
11-
3. gpt-5.1 Broad world knowledge with strong general reasoning.
8+
› 1. Mock ACP Agent Mock agent for testing purposes.
9+
2. Gemini 2.0 Flash Thinking Google's experimental thinking model.
10+
3. Claude Anthropic's Claude via Agent Context Protocol.
11+
4. gpt-5.1-codex Optimized for codex.
12+
5. gpt-5.1-codex-mini Optimized for codex. Cheaper, faster, but
13+
less capable.
14+
6. gpt-5.1 Broad world knowledge with strong general
15+
reasoning.
1216

1317
Press enter to select reasoning effort, or esc to dismiss.

nori-releases.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Nori Releases
2+
3+
## Upstream Release Cadence
4+
5+
Upstream releases are very rapid (multiple releases per week):
6+
7+
- rust-v0.58.0: Nov 13
8+
- rust-v0.59.0: Nov 19
9+
- rust-v0.60.1: Nov 19 (same day!)
10+
- rust-v0.61.0: Nov 20
11+
- rust-v0.62.0: Nov 21
12+
- rust-v0.63.0: Nov 21 (same day!)
13+
14+
Release Workflow (from rust-release.yml):
15+
16+
1. Manual process: git tag -a rust-vX.Y.Z → git push origin rust-vX.Y.Z
17+
2. CI validates tag matches codex-rs/Cargo.toml version
18+
3. Builds multi-platform binaries with code signing
19+
4. Publishes to GitHub Releases and npm
20+
21+
## Branching Strategy
22+
23+
```
24+
upstream/main ──●──●──●──●──●──●──●──●──●──●──●──●──●──●──●──●───→
25+
│ ▲ ▲ ▲
26+
│ │0.61.0 │0.63.0 │future release
27+
│ │ │ │
28+
▼ │ │ │
29+
fork/upstream-sync ──────┴──────────────┴──────────────┴───────→
30+
│ │
31+
│ merge │ merge
32+
▼ ▼
33+
origin/dev ─────●────●────●────●────────●────●────●────────────→ (your ACP work)
34+
```
35+
36+
Branch Roles:
37+
38+
| Branch | Purpose |
39+
|--------------------|-----------------------------------------------|
40+
| origin/main | Stable releases of your fork |
41+
| origin/dev | Active development (ACP features) |
42+
| fork/upstream-main | Tracks upstream/main exactly (already exists) |
43+
| fork/upstream-sync | NEW: Sync point branch for merges |
44+
45+
## Automated Sync (CI)
46+
47+
The `upstream-sync` GitHub Actions workflow automatically detects new stable
48+
upstream releases and creates draft PRs.
49+
50+
**Trigger:** Daily at 9 AM UTC (scheduled) or manual via workflow_dispatch
51+
52+
**What it does:**
53+
54+
1. Fetches upstream tags
55+
2. Finds latest stable tag (X.Y.Z only, no alpha/beta)
56+
3. Updates `fork/upstream-sync` branch to point to the tag
57+
4. Creates `sync/upstream-vX.Y.Z` branch from the tag
58+
5. Opens a draft PR against `dev` with merge instructions
59+
60+
**Manual trigger:**
61+
62+
```bash
63+
# Sync latest stable release
64+
gh workflow run upstream-sync.yml
65+
66+
# Sync specific tag
67+
gh workflow run upstream-sync.yml -f tag=rust-v0.63.0
68+
69+
# Dry run (test without creating branches/PRs)
70+
gh workflow run upstream-sync.yml -f dry_run=true
71+
```
72+
73+
**Idempotency:** If a sync branch already exists, the workflow skips that release.
74+
75+
## Manual Sync Workflow
76+
77+
For manual syncing (or if CI is unavailable):
78+
79+
1. Update tracking branch
80+
```bash
81+
git fetch upstream --tags
82+
git branch -f fork/upstream-sync rust-v0.63.0
83+
git push origin fork/upstream-sync --force
84+
```
85+
86+
2. Create sync branch from the release tag
87+
```bash
88+
git checkout -b sync/upstream-v0.63.0 rust-v0.63.0
89+
git push origin sync/upstream-v0.63.0
90+
```
91+
92+
3. Merge into dev with conflict resolution
93+
```bash
94+
git checkout dev
95+
git merge sync/upstream-v0.63.0 --no-ff -m "Sync upstream rust-v0.63.0"
96+
```
97+
98+
4. Resolve conflicts, test, push
99+
```bash
100+
cd codex-rs && cargo test
101+
cargo insta review # if snapshot tests need updating
102+
git push origin dev
103+
```
104+
105+
## Downstream Nori Releases
106+
107+
For now we will maintain our own separate versioning scheme, to avoid blocking
108+
on the upstream releases for our release tagging.
109+
110+
For example for nori-v0.2.0 or similar:
111+
112+
git checkout main
113+
git merge dev --no-ff
114+
git tag -a nori-v0.2.0 -m "Nori release 0.2.0"
115+
git push origin main --tags
116+

0 commit comments

Comments
 (0)