You give Codex a small task (“rename this prop, update the call sites”). Twenty minutes later you check the PR list and there is none. You look at git log on main and the agent’s commits are sitting on top of main, no branch, no PR. Or it landed on feature/old-experiment from a teammate’s task three weeks ago. Or it created a branch but the final commit went somewhere else, so half the work is on agent/task-7 and half on agent/task-8.
Fastest fix: make the task start from a clean default branch and a fresh per-task branch every time, then protect main on the server so a stray push is refused. In cloud Codex that means setting the task’s base branch explicitly (cloud tasks default to your repo’s default branch as of June 2026); locally it means one Git worktree per task and a setup script that resets and branches before the agent touches anything. The rest of this article covers each cause, the exact commands, and how to rescue commits that already landed wrong.
First, figure out which Codex you are running
The two surfaces fail differently, so confirm which one you mean:
| Surface | Where work happens | Default branch behavior | Most common wrong-branch cause |
|---|---|---|---|
| Cloud Codex (task from chatgpt.com/codex or the GitHub integration) | A fresh, isolated microVM per task; destroyed on completion | Checks out the repo’s default branch unless you pick a base branch or @codex fix on a PR | Base branch defaulted to main; @codex fix ran against the default branch instead of the PR branch |
Local Codex app / CLI (codex on your machine) | Your checkout, or a Git worktree per task | Inherits whatever branch the checkout is on | UI shows one branch while the agent’s tool calls run in a different workdir; worktree name collision |
If there is no PR but there are commits, it is almost always a base-branch or worktree problem, not a flaky PR step. Verify the branch first.
Common causes
1. Cloud task used the default base branch
A cloud task provisions a fresh container and “checks out your repo at the selected branch or commit SHA.” If you did not pick a base branch, it defaults to your repo’s default branch (main on most repos). The agent’s commits become a new branch only when you open the PR; if the PR step is skipped or fails, the work is effectively stranded on the base.
How to spot it: the task summary shows a diff but you never clicked “Create PR,” or the created PR targets main when you expected a release branch. In the task’s environment settings the base branch is unset or main.
2. Local UI shows one branch, the agent works in another
In the local Codex app the visible prompt can sit on one directory/branch while the agent runs tool calls with a different effective working directory. You think work is happening on agent/task-12; the agent is committing in a worktree (or another repo) entirely. This is a known issue (openai/codex #23140, duplicate of #21712) as of June 2026.
How to spot it: the branch shown in the UI header has no new commits, but git log --all --oneline finds your changes on a different worktree path or branch. Run git worktree list and check each path’s HEAD.
3. git checkout -b collided with an existing branch and the agent kept going
The setup script ran git checkout -b agent/fix-issue-123, but that branch already existed from a previous run, or the name is checked out in another worktree. Git refuses (git worktree enforces one branch per worktree). The command errored on stderr; the agent only inspected stdout and committed on the still-current branch.
How to spot it: the transcript shows fatal: A branch named 'agent/fix-issue-123' already exists. or fatal: 'agent/fix-issue-123' is already used by worktree at ..., followed by commits anyway. The commits went wherever HEAD pointed.
4. Agent synced with main, then forgot to switch back
Codex was told “stay in sync with main.” It ran git checkout main && git pull, edited files, and committed — without switching back to the task branch. The commits land on main locally. If the harness then pushes main, it hits branch protection (best case) or succeeds (worst case).
How to spot it: the transcript contains git checkout main with no matching git checkout agent/... afterward. The new commits’ parent SHAs sit on main’s tip.
5. AGENTS.md says “commit your changes” with no branch rule
A well-meaning instruction said “commit your changes” without naming a branch. Codex defers to the most explicit instruction it has; with no branching rule it uses HEAD. If HEAD is main, your work lands on main.
How to spot it: grep -in 'commit\|branch\|push' AGENTS.md finds no “create a new branch first” rule. The agent will not invent one.
6. Two parallel runs shared a worktree or volume
You triggered two tasks at once and both mounted the same persistent volume (self-hosted harness) or the same worktree path. They raced on git checkout -b; one won, both committed, and the loser’s commits mixed into the winner’s branch.
How to spot it: a single branch contains interleaved commits from two unrelated tasks. Worker logs show overlapping timestamps on the same volume.
7. Branch name templated from an unset variable
The setup script computes the branch as agent/${TASK_ID} and TASK_ID was empty. git checkout -b agent/ either errors or creates a literal branch named agent/, depending on Git version.
How to spot it: a branch literally named agent/, agent, or ${TASK_ID} appears in git branch -a.
Shortest path to fix
Step 1: For cloud Codex, set the base branch on the task
Before launching the task, set the base branch explicitly instead of trusting the default:
- In the Codex web/task UI, the base branch is shown next to the repo selector. Change it from the repo default to the branch you actually want as the PR target.
- For
@codexon GitHub, comment on the PR itself (not the repo) so it works against the PR branch. If you comment on an issue, it branches from the default branch. - After the agent finishes, click Create PR and confirm the PR’s base branch is correct before merging. Cloud Codex names the head branch for you; you only choose the base.
This removes the single biggest cloud cause: an unset base silently meaning main.
Step 2: For local Codex, use one worktree per task
Git enforces one branch per worktree, which is exactly what you want for isolation. Create the worktree and start Codex inside it (there is no stable --worktree flag yet as of June 2026, so do this manually):
# Create an isolated worktree on a fresh branch, from up-to-date main
git fetch origin
git worktree add -b "agent/task-123" ../wt-task-123 origin/main
cd ../wt-task-123
codex
Each worktree has its own HEAD, so a second task cannot land on this branch by accident. Run git worktree list afterward to confirm the agent committed where you expect. For fully isolated per-worktree Codex config, set CODEX_HOME to a worktree-local path.
Step 3: In setup.sh, reset clean and branch before the agent runs
For any harness that starts on a reused checkout, force a clean state in .codex/setup.sh (or your task-init hook):
#!/usr/bin/env bash
set -euo pipefail
# Reset to a clean state, whatever the previous task left behind
git fetch origin
git checkout main
git reset --hard origin/main
git clean -fdx
# Create a fresh branch named after this task; abort if TASK_ID is empty
BRANCH="agent/${TASK_ID:?TASK_ID must be set}-$(date +%s)"
git checkout -b "$BRANCH" || git checkout -B "$BRANCH"
echo "Working on $BRANCH"
The :? guard refuses to proceed if TASK_ID is unset, killing the empty-template bug. The timestamp suffix prevents collisions on retry, and checkout -B resets the branch if a stale one survives.
Step 4: Pin the rule in AGENTS.md
Codex defers to the most explicit instruction it sees, so make this a top-of-file rule:
## Branching
- Always work on the branch created by setup.sh. Do not switch branches.
- Never run `git checkout main` mid-task.
- Never commit directly to main, master, develop, or release/*.
- To sync with main, run `git merge origin/main` from the task branch.
A specific rule near the top trumps the implicit “commit your changes” reading.
Step 5: Block direct pushes to protected branches on the server
This is the safety net for when everything above fails. In your GitHub repo settings, add a branch protection rule (or a ruleset) for main, master, develop, and release/*:
- Require a pull request before merging (blocks direct pushes)
- Block force pushes
- Require status checks to pass
Even if Codex tries to push to main, the server refuses. See GitHub’s branch protection docs for the exact toggles.
Step 6: Add a pre-push hook that refuses protected refs
Catch the bad push at the sandbox layer, before the server sees it. In .codex/setup.sh after creating the branch:
mkdir -p .git/hooks
cat > .git/hooks/pre-push <<'EOF'
#!/usr/bin/env bash
while read local_ref local_sha remote_ref remote_sha; do
case "$remote_ref" in
refs/heads/main|refs/heads/master|refs/heads/develop|refs/heads/release/*)
echo "ERROR: pushing to $remote_ref is forbidden for agent runs"
exit 1
;;
esac
done
EOF
chmod +x .git/hooks/pre-push
A server-side rejection sometimes leaves the sandbox in a confusing half-state, so failing locally first is cleaner.
Step 7: Verify HEAD in the commit step
Wrap the agent’s commit so it fails loudly if HEAD drifted off the task branch:
current=$(git rev-parse --abbrev-ref HEAD)
case "$current" in
agent/*) ;;
*) echo "Refusing to commit on $current"; exit 1 ;;
esac
git add -A
git commit -m "$1"
If anything switched HEAD off the task branch, the commit aborts instead of landing somewhere wrong.
Recovering when commits already landed wrong
If commits went to main locally but were not pushed:
# Save the bad commits onto a new branch (this just points a ref at HEAD)
git branch "agent/rescue-$(date +%s)"
# Reset main back to the remote
git fetch origin
git reset --hard origin/main
# Continue work on the rescue branch
git checkout "agent/rescue-..." # use the name printed above
If the push already happened:
- main is protected: your push was rejected, so just delete the local commits with the reset above. This is the case branch protection is designed to produce.
- main is unprotected and the push succeeded: use
git revert <oldest_bad>^..<newest_bad>to add reverting commits, then open a normal PR. Do notgit push --forceonmainto erase the commits; that rewrites shared history and breaks everyone else’s clone.
If a local commit landed in the wrong worktree, move it: from the correct worktree run git cherry-pick <sha>, then git reset --hard HEAD~1 in the wrong one.
How to confirm it’s fixed
- Run the task again and immediately check
git rev-parse --abbrev-ref HEAD(orgit worktree list) — it should be onagent/<task>, nevermain. - After the agent commits,
git log --all --oneline --decorateshould show the commits only on the task branch. - For cloud tasks, open the PR and confirm both the head and base branches are what you intended before merging.
- Audit recent runs for the same bug:
# Branches with an agent prefix, oldest first — any with commits but no PR is suspect
git for-each-ref --format='%(refname:short) %(committerdate:iso8601)' \
refs/remotes/origin/agent/ | sort -k2 | head -50
When it is not on you
If a self-hosted or vendor harness reuses worker pods without resetting the worktree, AGENTS.md rules and pre-push hooks cannot save you — the underlying state still leaks. File a bug with the harness owner; the fix has to be in the worker lifecycle. The local UI-vs-workdir mismatch (#23140 / #21712) is likewise an upstream bug; until it ships a fix, rely on explicit worktrees and the HEAD check in Step 7.
Prevention checklist
- Cloud: set the base branch on every task instead of trusting the repo default
- Local: one Git worktree per task; confirm with
git worktree list - Setup script resets to
origin/mainand creates a per-task branch with a timestamp suffix TASK_IDand any branch-name input is:?-guarded so empty values abort- AGENTS.md forbids
git checkout mainand direct commits to protected branches - Server-side branch protection on every protected branch pattern
- Pre-push hook in the sandbox refuses pushes to protected refs
- Commit wrapper verifies HEAD matches
agent/* - Weekly audit of
agent/*branches with no PR
FAQ
- Why did my cloud task land on main instead of a branch? Cloud tasks check out the repo’s default branch unless you pick a base, and the head branch is only created when you open a PR. Set the base branch on the task and click Create PR. As of June 2026 the default really is your repo’s default branch.
@codex fixedited the wrong branch — why? Comment on the PR itself, not on an issue or the repo. From an issue, Codex branches off the default branch; from a PR it works against that PR’s branch.- The UI shows my branch but commits are missing. Where did they go? The local app can run tool calls in a different effective workdir than the prompt shows (#23140). Run
git worktree listandgit log --all --onelineto find them, then cherry-pick into the right worktree. - Can I let the agent reuse one branch across tasks for context? No. Cross-task state leaks both ways — task A’s code shows up in task B’s PR, and reviewers cannot read the agent’s intent from the diff.
- My project merges feature branches into a long-lived integration branch. How do I protect it? Treat the integration branch like main: protect it, forbid direct commits, and require PRs even from the agent.
Related
- Codex modifies git history unintentionally
- Codex cannot finish patch
- Codex cannot resolve merge conflict
- Codex patch conflicts existing code
- Codex environment setup fails
- Codex PR too large to merge
- Codex PR description too generic
- Codex agent out of context on long repo
- Codex misses project conventions
- Codex ignores project structure
Tags: #Codex #AI coding #Troubleshooting