Files
clawtap/docs/superpowers/specs/2026-03-24-codex-uuid-discovery-fix.md
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

121 lines
6.4 KiB
Markdown

# 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) |