# Code Review: Executive Summary

**Project:** qc-trace
**Date:** 2026-02-07
**Reviewer:** Claude Opus 4.6
**Scope:** Full codebase (~12,000 lines Python + ~2,000 lines TypeScript/React + shell scripts + infra)
**Context:** ~13K lines added in a single day via parallel AI agents

---

## Overall Assessment

The architecture is sound — daemon watches local IDE files, transforms to a unified schema, pushes to an ingest server backed by Postgres, with a React dashboard for visualization. The normalize-on-read pattern with per-source schemas is well-designed.

However, the speed of development left significant issues: **~1,700 lines of dead code** (14% of the Python codebase), **no authentication on any endpoint**, **data loss paths in the daemon**, and **~37% of source code with zero test coverage**. The codebase needs a cleanup pass before it's production-ready.

---

## Findings by Severity

### Critical (15 total)

| # | Area | Finding | File(s) |
|---|------|---------|---------|
| 1 | **Server** | No authentication on any endpoint — any process can read/write all data | `server/app.py:80-124` |
| 2 | **Server** | Wildcard CORS (`*`) + no auth = any webpage can exfiltrate all trace data | `server/app.py:63,70` |
| 3 | **Server** | No POST body size limit — unbounded memory from a single request | `server/app.py:55-57` |
| 4 | **Daemon** | Gemini/Cursor collectors load entire files (~200MB) into memory | `daemon/collector.py:184,241` |
| 5 | **Daemon** | State advances even when push fails → silent permanent data loss | `daemon/main.py:137-149` |
| 6 | **Utils** | SQL injection in `sqlite.py` via f-string table/column interpolation | `utils/sqlite.py:192-193` |
| 7 | **Utils** | `sqlite.py` (390 lines) is entirely dead code — never imported | `utils/sqlite.py` |
| 8 | **Utils** | `schema_extractor.py` (466 lines) is entirely dead code | `utils/schema_extractor.py` |
| 9 | **Schemas** | Token double-counting in Claude Code (same TokenUsage on assistant + each tool_call) | `claude_code/transform.py:210,237` |
| 10 | **Schemas** | Non-deterministic UUIDs in Codex breaks idempotent ingestion | `codex_cli/transform.py:37` |
| 11 | **Tests** | Hardcoded local paths (`/home/sagar/trace`, `~/.claude/`) will fail in CI | Multiple test files |
| 12 | **Tests** | Zero coverage for daemon main loop (`run()`, `_poll_cycle()`) | `daemon/main.py` |
| 13 | **Tests** | Zero coverage for entire DB reader layer (5 functions, 152 lines) | `db/reader.py` |
| 14 | **Dashboard** | SessionDetail downloads ALL sessions to find one (no single-session endpoint) | `SessionDetail.tsx` |
| 15 | **Infra** | Service files reference `/usr/bin/python3` which won't have `qc_trace` installed | `scripts/*.plist`, `scripts/*.service` |

### High (20 total)

| # | Area | Finding |
|---|------|---------|
| 1 | Server | `_make_flush_callback` in `app.py` is dead code |
| 2 | Server | Batch accumulator re-queues failures with no cap → unbounded memory growth |
| 3 | Server | `BatchWriter.write()` inflates counters (returns len even when ON CONFLICT skips) |
| 4 | Server | No explicit rollback on partial COPY failure |
| 5 | Server | Reader mutates `conn.row_factory` on shared connection (race condition) |
| 6 | Daemon | `progress.py` (129 lines) is entirely dead code — orchestration utility misplaced in daemon |
| 7 | Daemon | `state.py` has `remove_state` and `reset_state` with identical implementations; `remove_state` never called |
| 8 | Daemon | Codex collector replays entire file from line 0 on every poll cycle (O(n) per poll) |
| 9 | Daemon | Backoff only partially applied (between batches, not between files or cycles) |
| 10 | Utils | Duplicate slug decoders — `cursor_parser._decode_workspace_slug` is broken, `repo_resolver.decode_cursor_slug` works correctly |
| 11 | Utils | File descriptor leak in `traced.py:136` — `open()` never closed after `Popen` |
| 12 | Utils | 3 cursor_parser functions (112 lines) are test-only dead code |
| 13 | Schemas | Codex `function_call_output` schema/transform mismatch (dict vs string) |
| 14 | Tests | `test_daemon_reconcile.py` uses hardcoded ports (18881-18887) — flaky |
| 15 | Tests | 858 lines in utils have zero test coverage |
| 16 | Tests | Dashboard API handlers entirely untested |
| 17 | Tests | `test_ingest_server.py` manually manages event loops ~20 times |
| 18 | Dashboard | `usePolling` fragile against unstable `fetcher` references (infinite re-render risk) |
| 19 | Dashboard | Overview page fires duplicate `/api/stats` from two components |
| 20 | Infra | `pyproject.toml` declares zero runtime deps despite needing `psycopg` |

---

## Dead Code Inventory

| File | Lines | Status |
|------|------:|--------|
| `utils/schema_extractor.py` | 466 | **100% dead** — never imported by any consumer |
| `utils/sqlite.py` | 390 | **100% dead** — never imported by any consumer |
| `daemon/progress.py` | 129 | **100% dead** — orchestration utility, not a daemon component |
| `cursor_parser` (3 functions) | 112 | **Dead** — `parse_mcp_tool_definition`, `parse_mcp_server_metadata`, `discover_cursor_projects` |
| `unified.py` (2 Literals) | ~15 | **Dead** — `ConversationMessageType`, `ObservabilityMessageType` never used |
| `codex_cli/v1.py` (~42 TypedDicts) | ~900 | **Effectively dead** — only ~8 of 50 types consumed by transform |
| `state.py:remove_state` | ~10 | **Dead** — never called from production code |
| `server/app.py:_make_flush_callback` | ~10 | **Dead** — never called |
| **Total dead code** | **~2,032** | **~17% of Python codebase** |

---

## Architecture Diagram (Actual vs Documented)

The README documents this flow:
```
Sources → Daemon → POST /ingest → Postgres → Dashboard
```

**What's actually wired:**
- Daemon → transforms → pusher → POST /ingest (works)
- Server → batch writer → Postgres (works)
- Dashboard → GET /api/* → reader → Postgres (works, but untested)
- Daemon state reconciliation on restart (works)

**What's broken or missing:**
- No auth anywhere in the pipeline — **Decision:** add explicit email prompt + manual API key via `qc-traced setup` (not inferred from git config)
- No rate limiting or body size limits on ingest
- Dashboard fetches ALL sessions instead of paginating properly
- Service files won't start the daemon (wrong python path)
- `qc-traced serve` (localhost viewer from the plan) not implemented — only the full-stack dashboard exists

---

## Top 10 Recommendations (Priority Order)

1. **Add auth via explicit email + manual API key** — Add a `qc-traced setup` command that asks the user for their email and an API key (manually provisioned for now). Store in `~/.qc-trace/config.json`. Daemon sends `X-API-Key` + email header on every `/ingest` POST. Server validates against a hardcoded allowlist or env var. Replace CORS `*` with the dashboard origin. Automate key provisioning later when onboarding at scale.

2. **Delete dead code** — Remove `schema_extractor.py`, `sqlite.py`, `progress.py`, and the unused cursor_parser functions. That's ~1,100 lines gone immediately.

3. **Fix data loss** — Don't advance daemon state when push fails. Only persist state after successful delivery confirmation.

4. **Fix token double-counting** in Claude Code transform. Create separate `TokenUsage` instances for assistant vs tool_call messages.

5. **Make Codex IDs deterministic** — Use `uuid5(namespace, f"{path}:{line_num}")` instead of `uuid4()` for idempotent reprocessing.

6. **Add POST body size limit** — `Content-Length` check in the server, reject > 10MB.

7. **Stream large files** in Gemini/Cursor collectors instead of loading 100MB into memory. Use chunked hashing.

8. **Fix test portability** — Replace hardcoded paths with `tmp_path` fixtures. Use port 0 for reconcile tests.

9. **Add DB reader tests** — The entire read API path (5 functions) has zero coverage.

10. **Fix service files** — Use the `PYTHON_PATH` placeholder as documented, or detect at install time.

---

## Detailed Reviews

| Review | File |
|--------|------|
| Schemas & Transforms | [01-schemas-and-transforms.md](./01-schemas-and-transforms.md) |
| Daemon | [02-daemon.md](./02-daemon.md) |
| Server & Database | [03-server-and-db.md](./03-server-and-db.md) |
| Utils & CLI | [04-utils-and-cli.md](./04-utils-and-cli.md) |
| Tests | [05-tests.md](./05-tests.md) |
| Dashboard & Infrastructure | [06-dashboard-and-infra.md](./06-dashboard-and-infra.md) |
