Files
clawtap/docs/superpowers/specs/2026-03-24-codex-uuid-discovery-fix.md
T
kuannnn 42861ea7fa feat: ClawTap v0.1.0 — initial release
Multi-adapter mobile UI for AI coding assistants.
Supports Claude Code, Codex CLI, and Gemini CLI through one interface.

Features:
- Real-time bidirectional sync via tmux + WebSocket
- Cross-AI review (send one AI's output to another for review)
- Multi-review tabs with minimize/expand
- Push notifications (PWA) with smart session-aware filtering
- Three-channel event system (hooks, file watcher, pane monitor)
- Voice input, image paste, draft persistence
- Terminal-native design (JetBrains Mono, dark theme, pixel art claw)
- CLI with --adapter flag on every command
- Zero-overhead fire-and-forget hooks
2026-03-26 10:40:26 +08:00

6.4 KiB

Codex UUID Discovery Fix + Session Architecture Cleanup

Problems Found

1. Deadlock: _waitForCliUUID blocks startSession (Critical)

startSession() calls _waitForCliUUID() which polls for session.cliSessionId to be set. But the UUID is only set when handleSessionStart hook fires, which requires Codex to process a prompt. The prompt is sent AFTER startSession returns. Deadlock: 15-second timeout, session creation fails.

Affects both handleQuery (new Codex chat from Web UI) and POST /api/reviews (Cross-AI Review child session).

2. Pending Session Matching by Count (Medium)

handleSessionStart matches hook to pending session by checking pendingSessions.length === 1. If 0 pending: treated as desktop-started. If 2 pending: neither matches, hook creates a spurious session entry. This is a guess, not a precise match.

3. _findAndAttachWindow uses command.includes('codex') (Medium)

Grabs the first tmux window whose command contains codex. If multiple codex windows exist, picks the wrong one. After Session ID Unification, window names are UUIDs, so this method is both incorrect and unnecessary (see solution).

4. _watchForTranscript matches by recency (Low)

Scans the day directory for JSONL files modified within 120 seconds, picks the first match. If two Codex sessions start simultaneously, can pick the wrong file.

5. Server shutdown leaves tmux windows running (Resource waste)

adapter.destroy() cleans up monitors and watchers but does NOT kill tmux windows. After server stops, CLI processes continue running in tmux, consuming resources.

6. DB sessions table is unnecessary (Complexity)

The sessions DB table stores id, cwd, window_id, adapter. After the Session ID Unification, all runtime data is in the in-memory sessions Map. The DB was used for:

  • _findAndAttachWindow window recovery after restart: unnecessary if windows are killed on shutdown
  • handleReconnect cwd lookup for resumeSession: unnecessary if handleReconnect doesn't resume
  • Review endpoints cwd lookup: can use in-memory Map instead

Solution

A. Remove _waitForCliUUID entirely

startSession() returns the temp key immediately. UUID discovery happens asynchronously via handleSessionStart or _watchForTranscript.

B. CODETAP_REF marker for precise matching

Every first message sent to a new Codex session includes a marker:

[CODETAP_REF:codex-1774316492094]
actual prompt or context here...

Where codex-1774316492094 is the temp key (tmux window name at creation time).

Injection points:

  • handleQuery in session-manager.ts: when creating a new session (no existing sessionId), prepend marker to the prompt
  • POST /api/reviews in index.ts: prepend marker to the context

Matching in handleSessionStart:

  1. Read the JSONL file at body.transcript_path
  2. Find the first user message
  3. Extract CODETAP_REF:xxx marker
  4. Match xxx to a pending session's temp key
  5. Call _rekeyAndRename to finalize

Matching in _watchForTranscript:

  • After finding a candidate JSONL file, verify it contains CODETAP_REF:tempKey

Frontend filtering:

  • Strip [CODETAP_REF:...] from user messages in convertMessages (useChat.ts)

C. _rekeyAndRename — finalize UUID discovery

New method called when UUID is discovered (by handleSessionStart or _watchForTranscript):

  • Delete temp key from sessions Map
  • Set CLI UUID as new key
  • Rename tmux window from temp name to CLI UUID
  • Update monitor's sessionId

D. Server shutdown kills all tmux windows

adapter.destroy() calls tmuxManager.killSession() to kill the entire codetap tmux session. No resource leaks.

E. Remove _findAndAttachWindow

With shutdown killing all windows, no tmux windows survive restart. No need to rediscover windows. Delete the method and all call sites.

F. Remove DB sessions table

The sessions table serves no purpose after changes D and G:

  • _findAndAttachWindow (deleted in E) was the main consumer
  • handleReconnect no longer calls resumeSession (changed in G)
  • Review endpoints get cwd from in-memory Map (changed in H)

Delete: CREATE TABLE, prepared statements, SessionRow interface, sessions export, all dbSessions.* calls across the codebase.

DB retains only session_reviews table (for Cross-AI Review).

G. Simplify handleReconnect

Remove the hasActiveWindow + resumeSession block. After shutdown kills windows, there is no scenario where a session is not in the Map but has an active tmux window.

handleReconnect becomes: register client, load JSONL history, replay pending state. Building tmux windows is handleQuery's job (when the user sends a message).

H. Review endpoints get cwd from Map + add parent_adapter to session_reviews

Replace dbSessions.get(parentCliSessionId) with adapter.getSession(parentCliSessionId) to get cwd from the in-memory Map. The parent session is always active (user is interacting with it) so it is always in the Map.

Add parent_adapter TEXT NOT NULL column to session_reviews table. Store it when creating a review. This way send-back and delete endpoints can find the correct adapter directly from the review row, without needing to iterate all adapters or query the sessions DB.

Files Affected

File Changes
server/adapters/codex/codex-tmux-adapter.ts A: remove _waitForCliUUID. B: add _matchByTranscriptMarker. C: add _rekeyAndRename. D: destroy calls killSession. E: remove _findAndAttachWindow. F: remove all dbSessions calls
server/adapters/claude/tmux-adapter.ts D: destroy calls killSession. F: remove all dbSessions calls
server/adapters/claude/index.ts No change (doesn't use dbSessions directly)
server/adapters/codex/index.ts No change
server/session-manager.ts B: inject marker in handleQuery. F: remove dbSessions import and calls. G: simplify handleReconnect. H: review restoration uses adapter.getSession for cwd
server/index.ts B: inject marker in POST /api/reviews. F: remove dbSessions import, clearAll call in shutdown. H: review endpoints use adapter.getSession for cwd
server/db.ts F: delete sessions table schema, SessionRow, prepared statements, sessions export. Keep session_reviews
src/lib/content-utils.ts B: add stripMarker function
src/hooks/useChat.ts B: strip marker in convertMessages
bin/codetap F: remove SQL queries that reference sessions table (get_project_sessions, -a listing, --resume lookup)