Codex Desktop pet tray omits active threads and shows inconsistent thread status

Open 💬 8 comments Opened May 24, 2026 by gritsev

Environment

  • Codex Desktop: 26.519.41501
  • Platform: macOS
  • Pet: BSOD
  • Observed: 2026-05-24 around 06:26-06:32 UTC / 10:26-10:32 Asia/Dubai

Expected

The pet tray should reflect the same active/recent in-flight threads as Codex Desktop's local thread/session state, and statuses should be consistent across comparable threads.

Actual

The pet tray showed only two thread cards:

  • Thread A: current diagnostics thread
  • Thread C: a routing task thread

But local Codex state showed three fresh, non-archived, non-final threads:

  • Thread A: current diagnostics thread, updated 06:31:58 UTC, visible in pet
  • Thread B: profile-menu task thread, updated 06:31:56 UTC, missing from pet
  • Thread C: routing task thread, updated 06:31:39 UTC, visible in pet

Thread B was missing from the pet tray even though it was updated at almost the same time as Thread C.

At the screenshot time window, Thread B and Thread C had very similar session state:

  • latest assistant phase: commentary
  • latest event: token_count
  • no final_answer
  • no task_complete

So the pet tray appears to apply inconsistent filtering: one unfinished/recent thread is shown, another similar unfinished/recent thread is hidden.

Local evidence

~/.codex/state_5.sqlite showed the three newest non-archived threads updated within ~20 seconds of each other.

~/.codex/sessions/2026/05/24/*.jsonl showed:

  • completed older thread: final_answer / task_complete, correctly absent from pet
  • Thread C: commentary / token_count, visible in pet
  • Thread B: commentary / token_count, missing from pet
  • Thread A: visible in pet

Desktop logs also showed the missing Thread B conversation was created and registered as a browser sidebar thread:

  • created at 06:16:03 UTC
  • registered at 06:16:03 UTC
  • continued receiving route/session events around 06:30-06:32 UTC

Why this matters

The pet becomes unreliable as a "what is still running / waiting / ready" surface. In this case it hid a likely active/recent non-final thread while showing another thread with comparable local state.

Related but not identical

  • #22112
  • #22625
  • #21348
  • #12321

This does not look like the external CLI case: the affected threads are local Codex Desktop/App threads, and the mismatch is between the pet tray and local Codex thread/session state.

View original on GitHub ↗

8 Comments

jshaofa-ui · 1 month ago

Solution: Fix Codex Desktop Pet Tray Omitting Active Threads

Issue: openai/codex #24302
Labels: bug, app, session, pets
Component: Pet Tray State Synchronization

---

1. Problem Statement

The Codex Desktop pet tray (system tray indicator) shows only a subset of active threads, omitting threads that are confirmed active in the local Codex session state. Specifically:

  • Pet tray shows: Thread A (diagnostics), Thread C (routing task) — 2 threads
  • Local state has: Thread A, Thread B (missing from tray), Thread C — 3 threads
  • Thread B was active, non-archived, non-final, updated at 06:31:58 UTC but absent from pet tray

This is a state synchronization bug where the pet tray's data source fails to reflect the complete set of active threads.

---

2. Root Cause Analysis

2.1 Architecture Overview

The pet tray is a native OS system tray indicator (macOS menu bar / Windows notification area) that displays thread status cards. It receives thread state updates through a subscription/observer pattern from the main application state.

Data Flow:

SessionManager (source of truth)
    ↓ (emits thread state changes)
ThreadStateStore (in-memory state)
    ↓ (subscribes to changes)
PetTrayController (UI controller)
    ↓ (renders)
PetTrayView (native OS tray UI)

2.2 Likely Root Causes (ranked by probability)

Cause A: Subscription Race Condition (Most Likely)

  • Thread B was created after the pet tray's initial subscription was established
  • The pet tray subscribes to "existing threads" at startup but doesn't listen for ThreadCreated events
  • Thread A and C existed before subscription; Thread B was created after

Evidence:

  • Thread B was "fresh" (recently created)
  • Pet tray showed only 2 of 3 threads consistently
  • The missing thread was non-archived and non-final (should be visible)

Cause B: Thread State Filtering Bug

  • The pet tray applies a filter that inadvertently excludes Thread B
  • Possible filter conditions: thread.status !== 'archived' && thread.status !== 'final' && thread.hasUI
  • If Thread B was launched programmatically (e.g., via Goal automation), it might lack a UI flag

Cause C: State Update Throttle/Debounce Bug

  • Pet tray updates are throttled (e.g., max 1 update per 500ms)
  • Thread B's state update arrived during a throttle window and was dropped
  • Subsequent updates for Thread B were coalesced with Thread A/C updates

---

3. Proposed Fix

3.1 Primary Fix: Ensure Complete Thread Subscription

File: src/main/pet-tray/pet-tray-controller.ts (or equivalent Rust/Tauri path)

// BEFORE (problematic):
class PetTrayController {
  constructor(private sessionManager: SessionManager) {
    // Only subscribes to changes, misses existing threads created after init
    this.sessionManager.onThreadChange((thread) => {
      this.updateTrayForThread(thread);
    });
  }
}

// AFTER (fixed):
class PetTrayController {
  constructor(private sessionManager: SessionManager) {
    // 1. Initialize with ALL active threads (not just listening for changes)
    const activeThreads = this.sessionManager.getActiveThreads();
    for (const thread of activeThreads) {
      this.addThreadToTray(thread);
    }
    
    // 2. Subscribe to ALL relevant events
    this.sessionManager.onThreadCreated((thread) => {
      this.addThreadToTray(thread);
    });
    this.sessionManager.onThreadChanged((thread) => {
      this.updateThreadInTray(thread);
    });
    this.sessionManager.onThreadArchived((threadId) => {
      this.removeThreadFromTray(threadId);
    });
  }
}

3.2 Secondary Fix: Remove Overly Aggressive Filtering

File: src/main/pet-tray/thread-filter.ts

// BEFORE:
function shouldShowInPetTray(thread: Thread): boolean {
  return thread.status !== 'archived' && thread.status !== 'final';
}

// AFTER:
function shouldShowInPetTray(thread: Thread): boolean {
  // Show all non-archived, non-final threads regardless of creation method
  return thread.status !== 'archived' 
    && thread.status !== 'final'
    && thread.status !== 'error' // optional: hide errored threads
    && !thread.isBackground; // optional: hide background-only threads
}

3.3 Tertiary Fix: Add Reconciliation Mechanism

Add a periodic reconciliation that compares pet tray state against session manager state:

// Reconcile every 30 seconds
setInterval(() => {
  const activeThreads = this.sessionManager.getActiveThreads();
  const trayThreadIds = new Set(this.trayThreads.map(t => t.id));
  const activeThreadIds = new Set(activeThreads.map(t => t.id));
  
  // Add missing threads
  for (const thread of activeThreads) {
    if (!trayThreadIds.has(thread.id)) {
      this.addThreadToTray(thread);
    }
  }
  
  // Remove stale threads
  for (const trayThreadId of trayThreadIds) {
    if (!activeThreadIds.has(trayThreadId)) {
      this.removeThreadFromTray(trayThreadId);
    }
  }
}, 30_000);

---

4. Testing Strategy

Unit Tests

  1. Pet tray initialized with 3 active threads → all 3 appear in tray
  2. Thread created after pet tray init → new thread appears in tray
  3. Thread archived → removed from tray
  4. Thread status changed → tray card updates
  5. Rapid thread creation (5 in 1 second) → all 5 appear

Integration Tests

  1. Launch Codex Desktop with 3 pre-existing threads → pet tray shows all 3
  2. Create a new thread while pet tray is visible → appears within 500ms
  3. Archive a thread → removed from pet tray within 500ms

Manual Testing

  1. Start Codex Desktop
  2. Create 3 threads (diagnostics, routing, custom)
  3. Verify pet tray shows all 3
  4. Archive one → verify it disappears
  5. Create another → verify it appears

---

5. Impact Assessment

  • Severity: Medium — users lose visibility into active threads
  • User Impact: Users may not realize threads are running, leading to resource waste or confusion
  • Risk of Fix: Low — adding subscription listeners and reconciliation is additive
  • Breaking Changes: None

---

6. Files to Modify

| File | Change |
|------|--------|
| src/main/pet-tray/pet-tray-controller.ts | Add onThreadCreated subscription + initial sync |
| src/main/pet-tray/thread-filter.ts | Remove overly aggressive filtering |
| src/main/pet-tray/reconciliation.ts (new) | Add periodic state reconciliation |
| tests/pet-tray.test.ts | Add regression tests |

---

7. Estimated Effort

  • Implementation: 2-4 hours
  • Testing: 1-2 hours
  • Review: 1 hour
  • Total: 4-7 hours
yevon · 1 month ago

I was just about to talk about this, the pets only sow the messages if the chat is new, but it never shows follow-up messages.

fbdesignpro · 1 month ago

Yeah synchronization of the pet with what's going on is lost pretty quickly, making the whole feature useless until this is fixed!

samuelbushi · 9 days ago

I can reproduce this deterministically in desktop build 26.707.41301 on macOS 26.3 (Darwin 25.3.0 arm64).

Deterministic trigger

  1. Wake the floating pet.
  2. Start a local task and confirm it appears as Running in the pet activity tray.
  3. Let the task continue for more than 180 seconds without an event that advances the conversation's updatedAt timestamp (for example, a long quiet command/tool operation).
  4. The task disappears from the pet tray even though its status remains running and the task is still working.
  5. A later progress event that advances updatedAt can make it reappear temporarily.

Root cause observed in the installed renderer

The session-to-notification filter computes an expiry for every non-idle status. For running, the expiry is hard-coded to:

updatedAtMs + 180 * 1e3

Once Date.now() reaches that value, the notification is omitted without first checking whether the session still has status === "running". The tray may refetch session data, but a quiet task retains the old updatedAtMs, so refetching does not prevent expiry.

This explains why synchronization is "lost pretty quickly": the tray is treating a live running state as a stale notification.

Expected fix

Running sessions should remain visible until their status changes. At the expiry switch, the minimal behavior would be equivalent to:

case "running":
  return null; // no expiry while the session is still running

TTL behavior can remain for terminal/unread states such as failed or review. This would also match the documented pet status semantics: Running — A task is actively working.

LogicLeapLtd · 6 days ago

Confirmed this is still present in Codex Desktop 26.707.72221 (build 5307) on macOS 26.5.2 (25F84, Apple Silicon).

Observed behavior: the pet activity tray tracks only a subset of currently active local chats; long-running/quiet chats disappear even though their tasks are still active in Codex.

Fresh bundle check on this build shows the same live-session expiry described above is still shipped:

case "running":
  return updatedAtMs + 180 * 1e3;

The notification filter then removes the session when Date.now() reaches that timestamp without first checking that the session is still running. The overlay is also built from a recent-conversations view rather than an explicit reconciled inventory of all non-idle threads.

This is therefore not a spritesheet/custom-pet packaging problem. A durable fix should keep every live running/waiting session present until its actual status changes and reconcile the tray against the complete active-thread set after subscriptions/refetches.

abuinitski · 5 days ago

Pets are actually a very strong codex feature for me - helps with context switching, especially while working with tasks in parallel, keeping track of what's currently active for me and what's done is essential while I distract for something else, waiting for Codex to finish current scope. Helps maintaining that gentle context awareness, and task switching is suddenly no that painful.

However, it only works when it's consistent in what it shows. Currently, it's completely not. So thumbs up for fixing please!

EunHyeokJung · 1 day ago

I can reproduce this on macOS with Codex Desktop 26.715.31925.

I often have multiple tasks actively running at the same time, but the Pet activity tray shows only a subset of them. The missing tasks are still running normally in the main Codex UI, so the Pet cannot currently be relied on to monitor parallel tasks.

This appears consistent with the active-thread synchronization and expiry behavior described in this issue.

fbdesignpro · 1 day ago

Yup, things still just "disappear" when tasks are in flight (tried to identify if it was when windows were switching or after a certain time but didn't find anything yet).

Unfortunately defeats the purpose of having it at all which is a shame!