Native goal continuation emits UUIDv4 turn IDs despite the app-server UUIDv7 contract

Open 💬 0 comments Opened Aug 5, 2026 by TopGrd

What issue are you seeing?

With native goals enabled, an idle thread with an active goal can start an automatic continuation whose public app-server turn ID is UUIDv4.

The app-server Turn contract explicitly says: Codex-generated turn IDs are UUIDv7. However, the automatic goal path calls try_start_turn_if_idle, which currently creates its TurnContext with Uuid::new_v4(). The resulting UUIDv4 is exposed by turn/started, item/*, turn/completed, persisted turn metadata, and active-turn control RPCs.

This is observable without sending another turn/start: the goal-created turn itself already violates the public ID contract. Clients can rely on the documented UUIDv7 property for timestamp ordering and correlation, so the difference is not only cosmetic.

Environment:

  • Codex CLI: 0.146.0
  • Platform: Darwin 25.5.0 arm64
  • Also present on public main at ed2f985a26eee9a59cde0fdefd20f69b45bc25f5

What steps can reproduce the bug?

  1. Start app-server with native goals and the experimental API enabled.
  2. Initialize a materialized thread.
  3. Set an active goal with thread/goal/set, for example:

``json
{
"method": "thread/goal/set",
"id": 3,
"params": {
"threadId": "<thread-id>",
"objective": "Inspect the workspace and report the result.",
"status": "active"
}
}
``

  1. Leave the thread idle so the goal runtime starts its automatic continuation.
  2. Read the next turn/started notification.
  3. Inspect params.turn.id. Its UUID version nibble is 4, not 7.

The current source path is:

  • codex-rs/ext/goal/src/runtime.rs: active goal continuation calls thread.try_start_turn_if_idle(...).
  • codex-rs/core/src/session/inject.rs: try_start_turn_if_idle calls new_default_turn_with_sub_id(uuid::Uuid::new_v4().to_string()).
  • codex-rs/core/src/tasks/regular.rs: turn/started exposes that TurnContext.sub_id as the lifecycle turn ID.

What is the expected behavior?

Every Codex-generated turn ID exposed through app-server should be UUIDv7, including turns started automatically by native goal continuation.

The turn/started, item/*, turn/completed, persisted metadata, and active-turn control APIs should continue using one stable ID, but that ID should satisfy the documented UUIDv7 contract.

Additional information

Related: #36866 reports a separate correlation defect when a later turn/start returns a provisional UUIDv7 submission ID while input is steered into an already-active goal turn. This report is narrower and independently reproducible: the native goal continuation produces a UUIDv4 public turn ID even when no later turn/start is sent.

View original on GitHub ↗