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
210 changes: 210 additions & 0 deletions .github/workflows/upstream-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# Automatically sync stable upstream releases into dev branch.
# Creates a sync branch and draft PR for each new stable release.
#
# Manual trigger: gh workflow run upstream-sync.yml
# With specific tag: gh workflow run upstream-sync.yml -f tag=rust-v0.63.0
# Dry run: gh workflow run upstream-sync.yml -f dry_run=true

name: upstream-sync

on:
schedule:
# Daily at 9 AM UTC (reasonable time for review in US timezones)
- cron: '0 9 * * *'
workflow_dispatch:
inputs:
tag:
description: 'Specific tag to sync (optional, defaults to latest stable)'
required: false
type: string
dry_run:
description: 'Dry run mode (log actions without creating branches/PRs)'
required: false
type: boolean
default: false

env:
UPSTREAM_REMOTE: https://github.com/openai/codex.git

jobs:
sync:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Add upstream remote and fetch tags
run: |
git remote add upstream "$UPSTREAM_REMOTE" || git remote set-url upstream "$UPSTREAM_REMOTE"
git fetch upstream --tags --force

- name: Determine target tag
id: tag
run: |
set -euo pipefail

if [[ -n "${{ inputs.tag }}" ]]; then
target_tag="${{ inputs.tag }}"
echo "Using manually specified tag: $target_tag"
else
# Find latest stable tag (X.Y.Z only, no alpha/beta/rc)
# Pattern: rust-v followed by semver without prerelease suffix
target_tag=$(git tag -l 'rust-v*' \
| grep -E '^rust-v[0-9]+\.[0-9]+\.[0-9]+$' \
| sort -V \
| tail -1)
echo "Detected latest stable tag: $target_tag"
fi

if [[ -z "$target_tag" ]]; then
echo "::error::No stable upstream tag found"
exit 1
fi

# Validate tag exists
if ! git rev-parse "$target_tag" >/dev/null 2>&1; then
echo "::error::Tag $target_tag does not exist"
exit 1
fi

# Extract version for branch naming (rust-v0.63.0 -> v0.63.0)
version="${target_tag#rust-}"
sync_branch="sync/upstream-${version}"

echo "target_tag=$target_tag" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "sync_branch=$sync_branch" >> "$GITHUB_OUTPUT"

- name: Check if sync branch already exists
id: check
run: |
set -euo pipefail

sync_branch="${{ steps.tag.outputs.sync_branch }}"

# Check if branch exists on origin
if git ls-remote --heads origin "$sync_branch" | grep -q "$sync_branch"; then
echo "::notice::Sync branch $sync_branch already exists, skipping"
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "Sync branch $sync_branch does not exist, will create"
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Update fork/upstream-sync tracking branch
if: steps.check.outputs.exists == 'false'
run: |
set -euo pipefail

target_tag="${{ steps.tag.outputs.target_tag }}"
dry_run="${{ inputs.dry_run }}"

echo "Updating fork/upstream-sync to point to $target_tag"

if [[ "$dry_run" == "true" ]]; then
echo "::notice::DRY RUN: Would update fork/upstream-sync to $target_tag"
else
# Create or update the tracking branch
git branch -f fork/upstream-sync "$target_tag"
git push origin fork/upstream-sync --force
fi

- name: Create sync branch
if: steps.check.outputs.exists == 'false'
run: |
set -euo pipefail

target_tag="${{ steps.tag.outputs.target_tag }}"
sync_branch="${{ steps.tag.outputs.sync_branch }}"
dry_run="${{ inputs.dry_run }}"

echo "Creating sync branch $sync_branch from $target_tag"

if [[ "$dry_run" == "true" ]]; then
echo "::notice::DRY RUN: Would create branch $sync_branch from $target_tag"
else
git checkout -b "$sync_branch" "$target_tag"
git push origin "$sync_branch"
fi

- name: Create draft PR
if: steps.check.outputs.exists == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TARGET_TAG: ${{ steps.tag.outputs.target_tag }}
SYNC_BRANCH: ${{ steps.tag.outputs.sync_branch }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail

# Count commits between dev and the tag for context
commit_count=$(git rev-list --count origin/dev.."$TARGET_TAG" 2>/dev/null || echo "unknown")

pr_title="Sync upstream $TARGET_TAG"

# Build PR body using heredoc
pr_body=$(cat <<EOF
## Upstream Sync

This PR syncs changes from upstream release \`$TARGET_TAG\`.

### Summary

- **Upstream tag:** \`$TARGET_TAG\`
- **Commits to merge:** ~$commit_count
- **Release notes:** [GitHub Release](https://github.com/openai/codex/releases/tag/$TARGET_TAG)

### Merge Instructions

1. Review the changes for conflicts with our ACP fork work
2. Resolve any merge conflicts:
\`\`\`bash
git checkout dev
git merge $SYNC_BRANCH --no-ff
# Resolve conflicts if any
\`\`\`
3. Run tests: \`cd codex-rs && cargo test\`
4. Update snapshot tests if needed: \`cargo insta review\`
5. Mark as ready for review when satisfied

### After Merge

- Delete the \`$SYNC_BRANCH\` branch
- Consider tagging a new nori release if significant changes
EOF
)

if [[ "$DRY_RUN" == "true" ]]; then
echo "::notice::DRY RUN: Would create PR with title: $pr_title"
echo "PR body would be:"
echo "$pr_body"
else
gh pr create \
--base dev \
--head "$SYNC_BRANCH" \
--title "$pr_title" \
--body "$pr_body" \
--draft

echo "::notice::Created draft PR for $TARGET_TAG"
fi

- name: Summary
run: |
echo "## Upstream Sync Summary" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "- **Target tag:** ${{ steps.tag.outputs.target_tag }}" >> "$GITHUB_STEP_SUMMARY"
echo "- **Sync branch:** ${{ steps.tag.outputs.sync_branch }}" >> "$GITHUB_STEP_SUMMARY"
echo "- **Branch existed:** ${{ steps.check.outputs.exists }}" >> "$GITHUB_STEP_SUMMARY"
echo "- **Dry run:** ${{ inputs.dry_run || 'false' }}" >> "$GITHUB_STEP_SUMMARY"
3 changes: 3 additions & 0 deletions codex-rs/tui/config.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
[projects."/home/clifford/Documents/source/codex"]
trust_level = "untrusted"

[projects."/home/clifford/Documents/source/nori/cli"]
trust_level = "untrusted"
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ expression: terminal.backend()
" Select Model and Effort "
" Access legacy models by running codex -m <model_name> or in your config.toml "
" "
"› 1. gpt-5.1-codex (current) Optimized for codex. "
" 2. gpt-5.1-codex-mini Optimized for codex. Cheaper, faster, but less capable. "
" 3. gpt-5.1 Broad world knowledge with strong general reasoning. "
" 1. Mock ACP Agent Mock agent for testing purposes. "
" 2. Gemini 2.0 Flash Thinking Google's experimental thinking model. "
" 3. Claude Anthropic's Claude via Agent Context Protocol. "
"› 4. gpt-5.1-codex (current) Optimized for codex. "
" 5. gpt-5.1-codex-mini Optimized for codex. Cheaper, faster, but less capable. "
" 6. gpt-5.1 Broad world knowledge with strong general reasoning. "
" "
" Press enter to select reasoning effort, or esc to dismiss. "
" "
Expand All @@ -29,6 +32,3 @@ expression: terminal.backend()
" "
" "
" "
" "
" "
" "
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ expression: popup
Select Model and Effort
Access legacy models by running codex -m <model_name> or in your config.toml

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

Press enter to select reasoning effort, or esc to dismiss.
116 changes: 116 additions & 0 deletions nori-releases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Nori Releases

## Upstream Release Cadence

Upstream releases are very rapid (multiple releases per week):

- rust-v0.58.0: Nov 13
- rust-v0.59.0: Nov 19
- rust-v0.60.1: Nov 19 (same day!)
- rust-v0.61.0: Nov 20
- rust-v0.62.0: Nov 21
- rust-v0.63.0: Nov 21 (same day!)

Release Workflow (from rust-release.yml):

1. Manual process: git tag -a rust-vX.Y.Z → git push origin rust-vX.Y.Z
2. CI validates tag matches codex-rs/Cargo.toml version
3. Builds multi-platform binaries with code signing
4. Publishes to GitHub Releases and npm

## Branching Strategy

```
upstream/main ──●──●──●──●──●──●──●──●──●──●──●──●──●──●──●──●───→
│ ▲ ▲ ▲
│ │0.61.0 │0.63.0 │future release
│ │ │ │
▼ │ │ │
fork/upstream-sync ──────┴──────────────┴──────────────┴───────→
│ │
│ merge │ merge
▼ ▼
origin/dev ─────●────●────●────●────────●────●────●────────────→ (your ACP work)
```

Branch Roles:

| Branch | Purpose |
|--------------------|-----------------------------------------------|
| origin/main | Stable releases of your fork |
| origin/dev | Active development (ACP features) |
| fork/upstream-main | Tracks upstream/main exactly (already exists) |
| fork/upstream-sync | NEW: Sync point branch for merges |

## Automated Sync (CI)

The `upstream-sync` GitHub Actions workflow automatically detects new stable
upstream releases and creates draft PRs.

**Trigger:** Daily at 9 AM UTC (scheduled) or manual via workflow_dispatch

**What it does:**

1. Fetches upstream tags
2. Finds latest stable tag (X.Y.Z only, no alpha/beta)
3. Updates `fork/upstream-sync` branch to point to the tag
4. Creates `sync/upstream-vX.Y.Z` branch from the tag
5. Opens a draft PR against `dev` with merge instructions

**Manual trigger:**

```bash
# Sync latest stable release
gh workflow run upstream-sync.yml

# Sync specific tag
gh workflow run upstream-sync.yml -f tag=rust-v0.63.0

# Dry run (test without creating branches/PRs)
gh workflow run upstream-sync.yml -f dry_run=true
```

**Idempotency:** If a sync branch already exists, the workflow skips that release.

## Manual Sync Workflow

For manual syncing (or if CI is unavailable):

1. Update tracking branch
```bash
git fetch upstream --tags
git branch -f fork/upstream-sync rust-v0.63.0
git push origin fork/upstream-sync --force
```

2. Create sync branch from the release tag
```bash
git checkout -b sync/upstream-v0.63.0 rust-v0.63.0
git push origin sync/upstream-v0.63.0
```

3. Merge into dev with conflict resolution
```bash
git checkout dev
git merge sync/upstream-v0.63.0 --no-ff -m "Sync upstream rust-v0.63.0"
```

4. Resolve conflicts, test, push
```bash
cd codex-rs && cargo test
cargo insta review # if snapshot tests need updating
git push origin dev
```

## Downstream Nori Releases

For now we will maintain our own separate versioning scheme, to avoid blocking
on the upstream releases for our release tagging.

For example for nori-v0.2.0 or similar:

git checkout main
git merge dev --no-ff
git tag -a nori-v0.2.0 -m "Nori release 0.2.0"
git push origin main --tags

Loading