# Codefleet Improvement Plan

Audit performed 2026-03-15 by 4 parallel Opus agents analyzing all 9 source modules and 9 test files.

---

## HIGH IMPACT

### 1. Thread safety hole in `_start_stage()`
- **Where:** `workflow.py:288-291`
- **What:** `_start_stage()` is called from within a lock but fetches the workflow record again without the lock. Concurrent `on_stage_complete()` calls can race on the same stage.
- **Fix:** Either hold the lock through `_start_stage()` or pass the already-fetched record as a parameter instead of re-fetching.
- **Complexity:** medium

### 2. Exception swallowing hides real errors
- **Where:** `supervisor.py:348,510`; `workflow.py:116,235,369`
- **What:** 5+ `except Exception: pass` blocks silently discard errors in callbacks, prompt rendering, cleanup, and file tailing. Bugs in these paths are invisible.
- **Fix:** Log exceptions at minimum. For callback errors (`supervisor.py:348`), propagate or log at WARNING level. For prompt rendering (`workflow.py:369`), surface the parse failure in the stage result.
- **Complexity:** small

### 3. Redundant DB fetches after updates
- **Where:** `supervisor.py:296,342,419`
- **What:** `create_worker()`, `_on_worker_complete()`, and `cancel_worker()` all call `store.update_worker()` then immediately `store.get_worker()` to return the record. Three unnecessary round-trips per operation.
- **Fix:** Have `update_worker()` return the updated record, or build the return payload from the in-memory state that was just persisted.
- **Complexity:** small

### 4. `result_schema.py` is entirely redundant
- **Where:** `result_schema.py:1-40`
- **What:** 40-line module wraps `WorkerResult.model_validate()` with a custom exception. `validate_result_data()` is only called in tests, never in production. `ResultValidationError` adds no value over Pydantic's `ValidationError`.
- **Fix:** Delete module. Move `parse_result_file()` into `supervisor.py` as a private helper. Replace `ResultValidationError` catches with `pydantic.ValidationError`. Update tests to call `WorkerResult.model_validate()` directly.
- **Complexity:** small

### 5. No error handling in server.py MCP tools
- **Where:** `server.py:142-278`
- **What:** Zero try/except blocks in any tool function. Supervisor exceptions bubble up unhandled to MCP clients with raw tracebacks instead of structured error responses.
- **Fix:** Wrap each tool in try/except returning a consistent error dict: `{"error": str, "details": str}`. Catch `ValueError`, `RuntimeError`, and `FileNotFoundError` specifically.
- **Complexity:** medium

### 6. Stale in-memory record after store update
- **Where:** `workflow.py:120-130`
- **What:** `cancel_workflow()` calls `store.update_workflow()` then manually patches the local `record` object's fields. These manual patches never reach the DB and diverge from persisted state if another update follows.
- **Fix:** Remove manual patches (lines 128-130). Either re-fetch from store or have the update method return the fresh record.
- **Complexity:** trivial

---

## MEDIUM IMPACT

### 7. Misleading field name `codex_command`
- **Where:** `models.py:78`, `store.py` (column name)
- **What:** Field stores the CLI command for any executor (claude, gemini, codex) but is named `codex_command`, implying it's codex-specific.
- **Fix:** Rename to `command_json` or `executor_command`. Add a DB migration for the column rename.
- **Complexity:** trivial (rename + migration)

### 8. Duplicate SQL update/list logic
- **Where:** `store.py:192-232,278-322`
- **What:** `update_worker()`/`update_workflow()` implement nearly identical dynamic SQL UPDATE builders. `list_workers()`/`list_workflows()` duplicate status-filter query construction.
- **Fix:** Extract `_execute_update(table, pk_col, pk_val, updates, json_columns)` and `_build_status_filter(table, statuses)` helpers.
- **Complexity:** small

### 9. Unused model fields and enums
- **Where:** `models.py:35-58`
- **What:** `TestResult`, `TestStatus`, `ResultStatus.BLOCKED`, `WorkerResult.tests`, and `WorkerResult.commits` are defined but never read or branched on in production code. They add maintenance burden and confuse the API contract.
- **Fix:** Remove `TestResult`, `TestStatus`. Keep `ResultStatus` but add a TODO if BLOCKED handling is planned. Remove or mark `tests`/`commits` fields as deprecated.
- **Complexity:** trivial

### 10. Duplicate test helpers across files
- **Where:** `test_server.py:13-24`, `test_workflow.py:30-49`, `test_supervisor.py:114-118`, `test_store.py:20-41`, `test_models.py:133-154`
- **What:** `_fake_build()` defined 3x, `_make_record()` defined 2x, wait-for-terminal pattern repeated 8x across test files.
- **Fix:** Move `_fake_build()` variants, `_make_record()`, `_make_workflow()`, and `wait_for_terminal()` into `conftest.py`.
- **Complexity:** small

### 11. Migration error swallowing
- **Where:** `store.py:127-128`
- **What:** `except sqlite3.OperationalError: pass` catches all OperationalErrors, not just "column already exists". Masks syntax errors, permission issues, etc.
- **Fix:** Check error message contains "already exists" or use `PRAGMA table_info()` before attempting migration.
- **Complexity:** trivial

### 12. `GitError` raised but never specifically caught
- **Where:** `git_ops.py:6-7`; caught as generic `Exception` in `supervisor.py:349,369`
- **What:** Custom exception class exists but callers only catch `Exception`, defeating the purpose of typed errors.
- **Fix:** Catch `GitError` specifically in supervisor/workflow error handlers. Log git-specific context.
- **Complexity:** small

### 13. Missing store transaction rollback
- **Where:** `store.py:135-170,251-267`
- **What:** Insert methods call `conn.commit()` but have no rollback on failure. Partial state could persist on constraint violations.
- **Fix:** Wrap inserts in try/except with `conn.rollback()` on error, then re-raise.
- **Complexity:** small

### 14. No validation on timeout and spawn depth
- **Where:** `models.py:75`, `supervisor.py:29`
- **What:** `timeout_seconds` and `max_spawn_depth` accept negative/zero values without rejection.
- **Fix:** Add `Field(gt=0)` constraints on Pydantic models. Add assertions in supervisor constructor.
- **Complexity:** trivial

---

## LOW IMPACT

### 15. server.py `model_dump()` boilerplate
- **Where:** `server.py:142-260`
- **What:** 6+ tool functions follow identical `result = supervisor.method(...); return result.model_dump()` pattern.
- **Fix:** Extract `_dump(payload)` one-liner helper or use decorator.
- **Complexity:** trivial

### 16. `_SafeDict` over-engineered for single use
- **Where:** `workflow.py:25-29`
- **What:** Custom dict subclass used once in `_render_prompt()`. Replaceable with `defaultdict(str)`.
- **Fix:** Replace with `from collections import defaultdict; ctx = defaultdict(str, variables)`.
- **Complexity:** trivial

### 17. Unused `worktree_path` on `StageState`
- **Where:** `models.py:160`
- **What:** Field is populated in `workflow.py:326` but never read back. The path already lives on the worker record.
- **Fix:** Remove field from `StageState`. Remove assignment in workflow.py.
- **Complexity:** trivial

### 18. Always-true test assertion
- **Where:** `test_worker_runtime.py:258`
- **What:** `assert wp.is_running() or True` always passes. Comment says "might finish very fast" but the assertion is meaningless.
- **Fix:** Replace with `assert pid > 0` or remove entirely.
- **Complexity:** trivial

### 19. Duplicate `StageState` dict lookups
- **Where:** `workflow.py:341-342`
- **What:** `workflow.stage_states.get(i, StageState())` called twice in the same condition.
- **Fix:** Cache in local variable: `state = workflow.stage_states.get(i, StageState())`.
- **Complexity:** trivial

### 20. Parametrizable executor command tests
- **Where:** `test_worker_runtime.py:22-130`
- **What:** Three nearly identical test classes (`TestBuildCodexCommand`, `TestBuildGeminiCommand`, `TestBuildClaudeCommand`) with same test structure.
- **Fix:** Collapse into single `@pytest.mark.parametrize` test class with executor configs.
- **Complexity:** small

---

## CROSS-CUTTING THEMES

### A. Inconsistent exception handling
Custom exceptions (`GitError`, `ResultValidationError`) are defined and raised but caught as generic `Exception`. Multiple `except: pass` blocks silently discard errors. This is the biggest systemic issue — errors are invisible until they cascade into confusing failures downstream.

**Action:** Establish a pattern: catch specific exceptions, log all others, never silently pass.

### B. Post-update DB fetch pattern
Supervisor repeatedly calls `store.update_worker()` then immediately `store.get_worker()`. The update methods should return the updated record to eliminate redundant queries.

**Action:** Refactor `update_worker()` and `update_workflow()` to return the updated record.

### C. Test helper duplication
`_fake_build()`, `_make_record()`, and wait-for-terminal loops are copy-pasted across 3-4 test files instead of living in `conftest.py`.

**Action:** Centralize in conftest.py in a single pass.

### D. Dead schema fields
`TestResult`, `TestStatus`, `ResultStatus.BLOCKED`, `WorkerResult.tests`, `WorkerResult.commits` are defined in the protocol but never used in any logic path. They add maintenance burden and confuse the contract.

**Action:** Remove or deprecate in next minor version.

---

## Additional Findings

### Operational gap: Claude CLI hangs when spawned from within Claude Code
During this audit, we discovered that `claude` CLI subprocesses spawned via codefleet workers hang indefinitely when the parent process is already a Claude Code session. Workers produced zero bytes of output over 15 minutes. This blocks the `claude` executor entirely when codefleet is used as an MCP server inside Claude Code.

**Action:** Investigate whether an env var or flag prevents the conflict. Consider documenting this limitation or adding a pre-flight check.

### Stale MCP server after `pip install -e .`
The MCP server process caches the installed package at startup. Reinstalling the package (e.g., `uv pip install -e .`) does not take effect until the MCP server is restarted. This caused the `--effort xhigh` bug to persist across multiple workflow attempts despite the fix being on disk.

**Action:** Document that MCP server restart is required after code changes. Consider adding a version check in `healthcheck` that warns if installed version differs from source.
