Skip to content

Commit linking attaches an unrelated Codex session from another worktree when sessions share the Codex app-server daemon #2612

Description

@pletinsky

What happened?

Two Codex sessions worked in one repository at the same time: A in the main checkout, B in a linked worktree (codex --worktree). When A committed, B was guest-condensed into the checkpoint of A's commit, although B had not touched any of the committed files.

Expected: the checkpoint of A's commit contains only A's session.

This happened six times in one day in one repository: five commits picked up the same session from a ~/.codex/worktrees/<id>/<repo> worktree, and one picked up another.

No transcript is lost, but B's conversation up to that moment is attributed to a commit it had nothing to do with. The guest condensation also consumes B's checkpoint window, so B's own next checkpoint starts mid-conversation.

Steps to reproduce

  1. Codex CLI 0.157 or later with the default daemon_auto_start, and entire enable with the Codex integration in a repository.
  2. Start Codex in the main checkout (session A) and give it a task that ends with a commit.
  3. After A's turn has started, start Codex in a linked worktree of the same repository (codex --worktree, session B) and give it a long task.
  4. While B's turn is still running, let A commit.
  5. The checkpoint of A's commit contains both sessions, and .entire/logs/entire.log shows session guest-condensed from a sibling worktree for B.

These steps follow the code path described under Additional context and the incidents above; I have not run them as a controlled reproduction.

Entire CLI version

Entire CLI 0.11.3 (go1.27.1, linux/amd64). None of the files involved has changed on main since (2dbea8b).

OS and architecture

Debian GNU/Linux 12 (bookworm) container, Linux 6.12 x86_64

Agent

Codex CLI 0.158.0; the TUI sessions run inside codex app-server --managed-daemon

Terminal

herdr over SSH

Logs / debug output

From the post-commit hook of A's commit, with timestamps dropped and IDs and paths replaced:

{"level":"INFO","msg":"attribution calculated","session_id":"<B>","component":"attribution","agent_lines":14,"human_added":0,"human_modified":0,"human_removed":0,"total_committed":14,"agent_percentage":100,"accumulated_user_added":0,"accumulated_user_removed":0,"files_touched":12}
{"level":"INFO","msg":"session condensed","component":"checkpoint","strategy":"manual-commit","session_id":"<A>","checkpoint_id":"<CP>","checkpoints_condensed":1,"transcript_lines":308}
{"level":"INFO","msg":"session guest-condensed from a sibling worktree; shadow state untouched","component":"checkpoint","strategy":"manual-commit","session_id":"<B>","checkpoint_id":"<CP>","home_worktree":"~/.codex/worktrees/<id>/<repo>"}

The first line carries A's attribution, as recorded in A's checkpoint metadata, under B's session_id. B's session state records "owner": {"pid": <daemon pid>, "name": "codex"}, the PID of codex app-server --listen unix:// --managed-daemon.

Additional context

Root cause. Since Codex 0.157 the TUI hosts its sessions in one shared background process, codex app-server --managed-daemon (automatic start is on by default since openai/codex#47179). Entire records a session's owner as the nearest ancestor of the hook process that is not a shell or Entire itself (ResolveOwner), so every Codex session on the machine gets the same owner: the daemon.

On a commit, findSessionsForCommitLinking adds the session returned by findSessionByCommitAncestry to the worktree-matched sessions. The daemon is an ancestor of every commit a Codex session makes, so all Codex sessions of the repository match at the same depth, and isNearerOwner breaks the tie by the later LastInteractionTime, which moves at turn boundaries. B had started its turn after A's last turn boundary, so B won and was appended as a guest. Being ACTIVE, it was condensed without an overlap check, and the guest branch consumed its checkpoint window.

The recency tie-break is meant for one process hosting sessions one after another, the resume case its comment describes. The Codex daemon hosts them concurrently.

Caller resolution takes the same tie-break: resolveCallerIdentity also ranks with isNearerOwner, so at equal depth recency wins even when the environment names one of the tied sessions. That is consistent with the attribution calculated line above.

Suggested fix. Identify the committing session by the agent-published session variable (agent.CallerSessionCandidates()) whenever one is set. Codex injects CODEX_SESSION_ID into the environment of its shell commands (inject_session_env), and Entire already declares it as Codex's caller-session variable. It names the session whose command is running, which process ancestry can only approximate. I have not verified that the variable reaches the git hook when the session runs in the daemon.

Keep process ancestry for what the variable cannot do: ordering several claims, since a nested agent inherits its parent's variable and the nearest owner is the author, and covering agents that publish no variable. Where nothing but recency separates sessions that share one owner process, linking only the worktree-matched sessions looks safer than attaching one that may be foreign, with a notice like the one resolveWorktreeCandidates prints when it declines to guess between live worktrees. The committing session is still linked whenever it commits in its home worktree. Commit linking and caller resolution could share this rule.

#2531 relies on the same identification (it re-homes "the session that process ancestry identified"), so under a shared daemon it would inherit the misidentification.

Workaround. Start concurrent Codex sessions of one repository with codex --no-daemon, so that each runs in its own process.

Happy to send a PR once the approach is agreed.

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions