Subagent completion notification shows UUID agent_path instead of nickname in 0.131.0

Open 💬 7 comments Opened May 20, 2026 by maizaAvaro

What version of Codex is running?

0.131.0

Which model were you using?

Observed in a Codex CLI/API-backed session using spawned subagents. The exact model should not be material to the display issue.

What OS are you running?

Linux

What happened?

After upgrading from 0.130.0 to 0.131.0, spawned subagents no longer consistently display their friendly nickname in later notifications/completion events. The spawn response still includes a nickname, but the completion notification surfaces the UUID-like agent_path instead.

Example from a session:

{"agent_id":"019e42da-a5f5-71e0-b291-9b5a86182c89","nickname":"Aristotle"}

Later notification for the same subagent:

{"agent_path":"019e42da-a5f5-71e0-b291-9b5a86182c89","status":{"completed":"..."}}

In 0.130.0, subagent names were displayed as friendly names in this workflow, e.g. famous scientist/mathematician/author-style names. In 0.131.0, the UI/event surface is exposing the stable ID in places where the nickname was expected.

This looks possibly related to #21870, which changed TUI agent metadata hydration to avoid blocking during agent fan-out. That PR says nickname/role metadata is now filled from ThreadStarted and picker refreshes when available. This symptom looks like a fallback path is rendering agent_path/thread ID even though the nickname is available from spawn metadata.

Steps to reproduce

  1. Run Codex CLI 0.131.0.
  2. Spawn a subagent.
  3. Observe that the spawn result includes both an ID and a friendly nickname.
  4. Wait for the subagent to complete.
  5. Observe the completion/subagent notification.

Expected behavior

If a subagent nickname is available, subsequent subagent notifications/completion messages should display or include that nickname consistently, with UUID/thread ID as secondary trace metadata at most.

Actual behavior

The later notification displays the UUID-like agent_path instead of the friendly nickname, even though the spawn result included the nickname.

Why this matters

The nickname is much easier to track in multi-agent sessions. Showing raw UUIDs makes subagent status updates harder to scan and appears like a regression from 0.130.0.

Possible cause

This may be a metadata hydration/cache fallback issue introduced by the 0.131.0 TUI/app-server changes, especially #21870. The local receiver-thread cache may not be populated or consulted for the completion notification path, so the renderer falls back to agent_path.

Additional context

This was noticed alongside other 0.131.0 sandbox/TUI behavior changes, but the nickname issue is independent: the subagent itself completed successfully, and the spawn result did include the nickname.

View original on GitHub ↗

7 Comments

maizaAvaro · 2 months ago

I know this is trivial, but I actually enjoyed seeing the names, even if front-surfacing UUIDs makes debugging complex parallel workflows a much easier query... ymmv, but why not both?
I fully expect this to get lost in Cthulhu's backlog, but an effort was made nonetheless.

jshaofa-ui · 2 months ago

Technical Solution

Fix: Subagent Completion Notification Shows UUID agent_path Instead of Nickname

Issue Summary

Bug: After upgrading from 0.130.0 to 0.131.0, spawned subagents no longer consistently display their friendly nickname in completion notifications. The spawn response includes both an agent_id and a friendly nickname, but the completion notification surfaces the UUID-like agent_path instead.

Root Cause: The collab_agent_metadata cache in ChatWidget must be populated BEFORE any notification referencing the thread is processed. In 0.131.0, changes from PR #21870 (TUI agent metadata hydration) altered the timing of metadata population. If the completion notification arrives before set_collab_agent_metadata() is called for the subagent's thread, the renderer falls back to showing agent_path (UUID) instead of the nickname.

Regression: This worked in 0.130.0. The spawn result includes the nickname, but the completion notification path doesn't consult the spawn metadata — it only checks the collab_agent_metadata cache.

Location: codex-rs/tui/src/chatwidget.rs (metadata cache) + codex-rs/tui/src/app/thread_events.rs (notification handling)

---

Root Cause Analysis

Nickname Flow

  1. Spawn: Subagent is spawned with agent_id and nickname:

``json
{"agent_id":"019e42da-a5f5-71e0-b291-9b5a86182c89","nickname":"Aristotle"}
``

  1. Metadata Cache Population: ChatWidget::set_collab_agent_metadata() should be called to cache the nickname:

``rust
self.collab_agent_metadata.insert(thread_id, AgentMetadata {
agent_nickname: Some("Aristotle".to_string()),
agent_role: ...,
});
``

  1. Completion Notification: When the subagent completes:

``json
{"agent_path":"019e42da-a5f5-71e0-b291-9b5a86182c89","status":{"completed":"..."}}
`
The renderer looks up
collab_agent_metadata(thread_id). If found, displays nickname. If NOT found, falls back to agent_path`.

The Bug

In 0.131.0, the metadata cache population was moved to a later point in the lifecycle (PR #21870). If the completion notification arrives before the cache is populated, the renderer falls back to agent_path.

The issue is a race condition between:

  • set_collab_agent_metadata() (populates the cache)
  • TurnCompleted notification processing (reads the cache)

Evidence

  • The spawn response DOES include the nickname
  • The issue only appears in 0.131.0 (regression)
  • Related to PR #21870 which changed metadata hydration timing
  • The fallback path renders agent_path when cache miss occurs

---

Proposed Fix

Fix 1: Populate metadata cache from spawn response immediately

File: codex-rs/tui/src/app/thread_routing.rs or wherever subagent spawn is handled

// When a subagent is spawned, immediately cache its metadata
// BEFORE any notifications can arrive
fn handle_thread_spawn(&mut self, spawn_response: ThreadSpawnResponse) {
    let thread_id = spawn_response.thread_id.clone();
    let nickname = spawn_response.nickname.clone();
    let role = spawn_response.role.clone();
    
    // Cache metadata IMMEDIATELY, before processing any other events
    if let Some(chatwidget) = self.chatwidgets.get_mut(&thread_id) {
        chatwidget.set_collab_agent_metadata(
            thread_id.clone(),
            nickname,
            role,
        );
    }
    
    // Also cache in the navigation/picker layer
    self.upsert_agent_picker_thread(thread_id, nickname, role);
}

Fix 2: Fall back to spawn metadata in completion notification renderer

File: codex-rs/tui/src/multi_agents.rs (or wherever completion is rendered)

fn render_subagent_completion(
    &self,
    agent_path: &str,
    completion_status: &CompletionStatus,
) -> Line {
    // Try cache first
    if let Some(metadata) = self.collab_agent_metadata.get(agent_path) {
        if let Some(nickname) = &metadata.agent_nickname {
            return self.render_with_nickname(nickname, &metadata.agent_role);
        }
    }
    
    // Fall back to spawn metadata (stored at spawn time)
    if let Some(spawn_meta) = self.spawn_metadata_cache.get(agent_path) {
        return self.render_with_nickname(&spawn_meta.nickname, &spawn_meta.role);
    }
    
    // Last resort: show agent_path
    Line::from(format!("Agent {} completed", agent_path))
}

Fix 3: Ensure metadata is available from ThreadStarted notification

The ThreadStarted notification includes agent_nickname. Ensure this is cached before any completion notification can be processed:

// In thread_events.rs, when ThreadStarted is received:
ServerNotification::ThreadStarted(turn) => {
    if let Some(nickname) = &turn.agent_nickname {
        self.chatwidget.set_collab_agent_metadata(
            turn.turn.id.clone(),
            Some(nickname.clone()),
            turn.agent_role.clone(),
        );
    }
    self.active_turn_id = Some(turn.turn.id.clone());
}

---

Files Changed

| File | Change |
|------|--------|
| codex-rs/tui/src/app/thread_routing.rs | Cache metadata immediately on spawn |
| codex-rs/tui/src/app/thread_events.rs | Cache metadata on ThreadStarted notification |
| codex-rs/tui/src/multi_agents.rs | Add spawn metadata fallback in completion renderer |
| codex-rs/tui/src/chatwidget.rs | No change needed (cache API already correct) |

---

Risk Assessment

  • Risk Level: Low (UI-only fix, no data loss risk)
  • Impact: Improves multi-agent session usability
  • Migration: None needed

---

Solution developed through automated analysis. Happy to discuss further or provide PR if needed.

iqdoctor · 1 month ago

I can still reproduce the same class of issue in a current multi-agent session, and #24581 adds a second surface for the same metadata propagation problem.

Fresh examples from today:

• Spawned Franklin [architect] (gpt-5.4-mini high)
...
• Waiting for Franklin [architect]
...
• Finished waiting
  └ Franklin [architect]: Completed - ...

That is the desired display contract when nickname/role metadata is available.

In the same session, another spawned subagent fell back to the raw thread id in the transcript history:

• Spawned 019e758a-b7fe-71d2-b725-ac4d3504a963 (gpt-5.5 high)
  └ Critically review the repaired PR plan ...
...
• Waiting for 019e758a-b7fe-71d2-b725-ac4d3504a963
...
• Finished waiting
  └ 019e758a-b7fe-71d2-b725-ac4d3504a963: Completed - APPROVE ...

And the structured completion notification still exposes only the path/id shape in this surface:

{"agent_path":"019e758c-17b7-7073-9dac-db03bece8614","status":{"completed":"4. 2+2 equals 4."}}

So I think #24581 is correctly a duplicate of this issue, but it is useful evidence that the bug is not limited to the final completion notification. The same metadata loss/fallback can affect spawn/wait/history rendering when the event path has an agent id but not the nickname/role already known elsewhere.

Maintainer-aligned fix shape still seems to be:

  • carry optional nickname/role alongside the agent id in the app-server/thread item state used for collab agent tool calls;
  • cache/upgrade TUI receiver metadata from spawn, wait-begin, wait-end, and completion events whenever those fields are present;
  • keep the UUID/thread id as a fallback and trace handle, but do not use it as the primary user-facing label once nickname/role metadata is known.

Reference from the duplicate issue: candidate fork PR https://github.com/strato-space/codex/pull/1 (strato-space:fix/subagent-event-labels) implements that metadata propagation path.

iqdoctor · 1 month ago

Update after rebasing the candidate branch onto current upstream openai/codex:main (8e5f561697):

The original one-path fix was too narrow. The updated branch now preserves nickname/role metadata through the app-server CollabAgentState for all collab paths that already carry it: spawn, send-input, wait begin/end, close, and resume. That makes both live TUI rendering and replayed thread history able to label agents as Nickname [role] instead of falling back to raw thread IDs.

Representative code shape:

fn collab_agent_state_with_metadata(
    status: AgentStatus,
    agent_nickname: Option<String>,
    agent_role: Option<String>,
) -> CollabAgentState {
    let mut state = CollabAgentState::from(status);
    state.agent_nickname = agent_nickname;
    state.agent_role = agent_role;
    state
}

For metadata-bearing begin events with no real final status, the branch only uses synthetic PendingInit when nickname/role metadata exists; otherwise it keeps agents_states empty so legacy UUID fallback behavior is unchanged. For wait-end, agent_statuses is treated as the authoritative metadata-bearing source when present, with legacy statuses used only as the fallback.

Fresh validation after the upstream rebase:

git diff --check
cargo test -p codex-app-server-protocol collab -- --nocapture   # 13 passed
cargo test -p codex-tui collab_receiver_notification -- --nocapture   # 2 passed

Note: a first TUI test attempt after the rebase hit local disk exhaustion (No space left on device) while rebuilding dependencies; after cargo clean freed space, the same focused TUI test passed.

iqdoctor · 1 month ago

Thanks @jshaofa-ui — I think this diagnosis points in the right direction, but after working through a PR I would frame the fix slightly differently.

Short version: the proposed solution is a good TUI/cache-side explanation of the symptom, but the PR I opened tries to fix the metadata propagation at the app-server/protocol boundary instead of only adding another TUI fallback cache.

Comparison:

| Area | jshaofa-ui proposal | PR approach |
| --- | --- | --- |
| Diagnosis | ChatWidget.collab_agent_metadata may be missing when completion notifications render, so the UI falls back to agent_path | nickname/role metadata is not consistently carried through CollabAgentState for live and replayed collab events |
| Main fix | Populate TUI metadata cache earlier and/or add spawn metadata fallback in renderer | Add optional agent_nickname / agent_role to CollabAgentState and propagate them through event mapping + thread history |
| Scope | Mostly TUI-local (chatwidget, routing, renderer) | App-server protocol/schema + live event mapping + replay history + TUI cache hydration |
| Replay/thread snapshot | Not explicitly covered | Covered via thread_history.rs |
| External app-server consumers | Still may not see nickname in event payloads | Receive nickname/role in the canonical app-server payload |
| Risk | Smaller UI-only patch, but may only treat the symptom | Wider surface/schema change, but optional/backward-compatible and fixes the data plane |

The key distinction is:

  • The proposal assumes the nickname already exists somewhere and the UI needs to cache it earlier.
  • The PR makes nickname/role part of the authoritative collab agent state emitted by app-server live events and replay history.

That matters because if metadata is lost before reaching TUI, an earlier or additional TUI cache cannot reliably recover it. It also matters for replay/thread snapshots and any non-TUI consumer of the app-server protocol.

So I see the PR as a more general version of the same diagnosis: instead of relying on spawn-time cache timing or renderer fallback, carry the metadata in the canonical event payload and let TUI hydrate its existing navigation/cache from that authoritative metadata.

Current PR: https://github.com/strato-space/codex/pull/1

iqdoctor · 1 month ago

For an upstream/openai agent that cannot read third-party fork code, here is a clean-room implementation brief to reproduce the fix from first principles.

Task

Fix the multi-agent/collab TUI bug where subagent completion/wait/resume notifications can render the raw UUID-like agent_path / thread id instead of the friendly subagent nickname/role.

Problem statement

Friendly subagent metadata exists at spawn / collab-agent reference time, but it is not consistently available when later app-server notifications are converted into TUI history/navigation state. When the TUI cannot find metadata for a receiver thread, it falls back to displaying the raw thread id/agent path.

This must work for both:

  1. live app-server notifications, and
  2. replayed thread history / snapshots after switching or restoring threads.

Clean-room implementation direction

  1. Identify the app-server protocol type representing per-receiver collab agent state in collab tool calls. It currently carries status/message-like data. Extend it with optional, backward-compatible metadata fields:
  • agent_nickname: Option<String>
  • agent_role: Option<String> / equivalent internal role field
  1. Update the live event-to-app-server-notification mapping so every collab agent state that has access to receiver metadata carries it into that protocol state. Cover at least:
  • spawn begin/end
  • send begin/end
  • wait begin/end
  • close begin/end
  • resume begin/end
  1. For wait-end-like events, prefer the richer metadata-bearing status entries if available. Keep the legacy map/status fallback only for events that do not include the richer entries.
  1. Update thread-history/replay conversion in the same way. The replay path must emit the same metadata-bearing app-server items as the live path, otherwise thread switches/snapshots will regress.
  1. Update TUI receiver-thread/navigation hydration to read nickname/role from the collab-agent state embedded in ItemStarted / ItemCompleted collab tool-call notifications. When an existing receiver thread entry is present, upgrade missing nickname/role only when new metadata is available; do not erase existing metadata with None.
  1. Regenerate any JSON/TypeScript schema artifacts required by the app-server protocol change.

Acceptance criteria

  • A spawned/named subagent completion notification displays the friendly nickname/role, not the raw UUID/thread id.
  • Waiting on one or more subagents displays friendly names for completed/failed/not-found states when metadata is available.
  • The behavior survives thread switch / thread snapshot replay; it must not depend only on an in-memory TUI cache populated during the original live spawn.
  • Legacy events without metadata remain valid and continue to fall back safely to existing behavior.
  • Protocol/schema changes are backward-compatible: new fields are optional and omitted when unavailable.
  • Existing collab status semantics are unchanged: only display metadata propagation should change.

Suggested focused tests

  • Live mapping test: collab wait/send/resume/close completion with receiver nickname/role produces app-server CollabAgentState containing those fields.
  • Replay/history test: the same metadata appears when converting stored thread history into app-server items.
  • TUI routing/cache test: processing a collab ItemStarted or ItemCompleted notification with metadata creates or upgrades the receiver thread entry with nickname/role.
  • Regression test: if metadata is absent, existing fallback behavior remains unchanged and no empty placeholder metadata overwrites an existing nickname.

Validation commands

Run the relevant focused Rust tests for:

  • app-server protocol collab event mapping/history
  • TUI collab receiver notification/routing behavior

Then run formatting/diff checks used by the repository.

iqdoctor · 1 month ago

Current upstream check from openai/codex main fae270932065 (2026-06-09): this looks partially improved but not fully fixed.

Fresh local evidence from a current tmux multi-agent session (comfy-0:3, omx-supervisor) shows both paths in the same workflow:

• Spawned Ampere [architect] (gpt-5.4-mini high)
...
• Waiting for Ampere [architect]
...
• Finished waiting
  └ Ampere [architect]: Completed - ...

That is the desired friendly label behavior.

But a later Critic spawn/wait/completion in the same session still fell back to the raw agent/thread id:

• Spawned 019eac4d-899c-7e52-88b8-48e3b8e65c80 (gpt-5.5 high)
...
• Waiting for 019eac4d-899c-7e52-88b8-48e3b8e65c80
...
• Finished waiting
  └ 019eac4d-899c-7e52-88b8-48e3b8e65c80: Completed - ...

So the real current state is: nickname/role propagation works on some surfaces, but UUID fallback is still reachable when the event/rendering path does not carry or prefer the friendly metadata.

I also ran a minimal local $code-review pass over current main, using the repo-local code-review-* slices. Consolidated findings:

  1. codex-rs/app-server-protocol/src/protocol/v2/item.rs:1052CollabAgentState still only carries status/message, so app-server collab items cannot preserve nickname/role for live/replayed consumers. Add optional backward-compatible agent_nickname / agent_role fields and regenerate schemas.
  1. codex-rs/app-server-protocol/src/protocol/event_mapping.rs:231 — live wait completion still maps from legacy statuses and ignores richer metadata-bearing status entries when present, so wait/completion UI can render UUID labels. Prefer the metadata-bearing source when available and fall back to legacy status-only maps only for old events.
  1. codex-rs/app-server-protocol/src/protocol/thread_history.rs:641 — replay/history conversion drops spawn nickname/role when reconstructing collab items, so thread switch/restored history can still regress to raw IDs. Replay should emit the same metadata-bearing collab state as the live path.
  1. codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs:154SubAgentActivityEvent appears to drop the nickname computed just above, so downstream activity/completion notifications only get agent_path. Either carry optional nickname/role on the activity event/item or hydrate by agent_thread_id before rendering.
  1. codex-rs/tui/src/multi_agents.rs:298sub_agent_activity_history_cell renders agent_path directly and cannot consult cached nickname/role metadata, unlike collab tool rendering. Prefer nickname/role when known, with path as fallback/trace metadata.
  1. codex-rs/tui/src/app/agent_navigation.rs:278 — active agent labels currently prefer agent_path over agent_nickname/agent_role, so a thread with known friendly metadata can still render as the raw path once agent_path is recorded. Friendly metadata should be primary; raw path should be fallback.
  1. codex-rs/tui/src/app/session_lifecycle.rs:42/agent status previews pass only agent_path into AgentStatusThreadPreview, so the preview title can discard an AgentPickerThreadEntry's nickname/role. Carry a display label derived from nickname/role and retain path only as fallback/trace metadata.

Net: I would keep this issue open until both canonical app-server collab state and the remaining TUI activity/navigation/status surfaces consistently prefer Nickname [role] whenever metadata is known, while preserving UUID/path as fallback/debug trace.