Audit of core/, agents/sorcar/, server/, agents/vscode/
Redundancies, inconsistencies and race conditions. Date: 2026-09-02. Development model: claude-fable-5-1. Read-only review and debugging model: gpt-5.6-sol.
1. Summary
86 fixes in 45 source files: 45 defects found by the audit fan-out, 31 further findings from the first gpt-5.6-sol review, 10 from the second review of the fix pass.
Every behavioural fix was reproduced first by an end-to-end test that failed on the unfixed code (real processes, pipes, git repositories, SQLite databases, local HTTP servers, Playwright and jsdom; no mocks of the code under test). Changed lines and branches are covered by those tests; the few branches unreachable without test doubles are documented in the test files or marked # pragma: no cover with the reason.
New tests: 45 Python test files (185 test functions) under src/kiss/tests/ and 21 JavaScript suites (functional suites plus V8 line-coverage gates) under src/kiss/agents/vscode/test/.
Verification: uv run check --full passes (ruff, mypy, pyright, extension typecheck and lint). Full Python suite: 7,905 tests in 30 parallel splits, all green except one Playwright test that times out only under 30-way load and passes 8/8 in isolation. JS suite: see section 6.
Budget: about $55 of the $1,000 task budget; the gpt-5.6-sol share stayed under the 20% cap that the task set (the user later allowed up to 50%).
Figure 1. How the work was organised. Reviewers never edited code; every review finding was re-verified before a fix subagent was dispatched.Figure 2. Fixes per area and round (86 in total). "agents/vscode" includes the two install scripts the extension's update button runs.
2. Method
The four trees were split into eight partitions with disjoint file ownership (core base, core models, sorcar agents, sorcar infrastructure, server web, server core, webview main.js, extension host). Each claude-fable-5-1 subagent read its files in full, wrote a failing end-to-end test per suspected defect, fixed the root cause, ran the impacted existing tests, and wrote a report. Findings that needed a file outside the partition went to a cross-boundary note.
The cross-boundary notes were verified by hand. One claim was wrong and rejected: the webview's new_tab handler is not dead; chat_sorcar_agent.py emits it. The remaining items were fixed by two more subagents.
Four read-only gpt-5.6-sol reviewers (one per area) checked the changed code for missed wiring and regressions. Each finding was re-verified before a fix subagent was dispatched; findings judged not to be defects were left alone and are listed in section 5.
A second gpt-5.6-sol review of the fix pass (instructed not to invent problems) produced ten more verified findings, which were fixed the same way.
Final verification: lint and type checks, the complete JS suite, and the complete Python suite in 30 parallel splits.
3. Defects fixed in the audit fan-out (45)
Categories: race concurrent or cross-process interleaving; inconsistency two code paths or a docstring and its code disagree; redundancy duplicated logic that had already drifted or was about to.
3.1 src/kiss/core/
#
Category
Where
What was wrong
Fix
Test
1
inconsistency
kiss_agent.py_execute_step
The text-only implicit finish returned raw text without consulting the finish guard or hook, so Sorcar's "block finish while a user message is pending" guard was bypassed and callers expecting the structured finish contract (RelentlessAgent) got plain text.
Shared _implicit_finish_allowed() and _implicit_finish_result() used by both implicit-finish paths.
The model was built before the per-run state was reset; a second run() with a bad model name saved its trajectory over the previous run's file.
Reset all per-run state first.
test_audit0902_core_base_trajectory_reset.py
3
redundancyinconsistency
vscode_config.pyload_config/save_config
Two copies of the config-reading block; both let UnicodeDecodeError escape although bad JSON was handled.
One _read_stored_config() helper catching OSError and any ValueError.
test_audit0902_core_base_config_junk_bytes.py
4
doc
config.py
max_budget description named the wrong RelentlessAgent default.
Corrected.
n/a
5
race
models/model.py_CLIProcess
A grandchild that inherited the CLI's stdin pipe kept the prompt writer parked in write(2); the turn waited for its whole deadline and close() blocked for the grandchild's lifetime.
Writer thread tracked; send_prompt returns once the child exits; close() closes each pipe only when its own thread finished.
Claude Code accepted exit status −15 (SIGTERM) as success; Codex reported it. A killed claude returned truncated text as the answer.
One exit-status policy in _CLIProcess.raise_for_exit().
same file
7
redundancy
both CLI adapters
The whole turn skeleton (timeout, stall handling, interrupt handling, exit check) was duplicated line for line, which is how #6 drifted.
CLITextModel._cli_turn() context manager.
#5/#6 tests + existing lifecycle tests
8
inconsistency
anthropic_model.py_create_message
A connection dropped mid-thinking left the thinking bracket open (OpenAI and Gemini transports close it in finally); the retry's answer rendered as reasoning.
finally: _close_thinking_if_open(); duplicate local flag removed.
Five distinct preserve outcomes were collapsed to a bool, so the user was told "a pre-commit hook may have rejected the commit" when the worktree was really kept because a sub-agent was still writing or an ignored file could not be rescued.
Outcome recorded in _last_preserve_outcome; shared warning helpers.
The owner-pid stamp was written after git worktree add and never for pool spares; a peer process's reclaim in that window deleted a live worktree, and a live spare was discarded before the owner check ran.
create adds and stamps under the reclaim lock; owner-liveness check moved ahead of the spare discard.
test_audit0902_sorcar_infra_reclaim_live_owner.py (real second interpreter)
20
inconsistency
git_worktree.py_branch_is_expendable
--all also lists linked worktrees' HEADs, so a branch checked out in its own worktree was always "expendable" even with unique commits; the "spare has content" guard never fired.
The four-condition "spare holds foreign content" predicate copied into three destructive paths.
GitWorktreeOps.spare_has_content().
same file
22
redundancy
cron_agent.py
Re-implemented the daemon socket path precedence owned by daemon_client._resolve_sock_path.
Call the owner.
test_audit0902_sorcar_infra_cron_sock_path.py
23
doc
git_worktree.py reclaim docstring
Claimed a worktree without kiss-original is left untouched; the code reclaims it with a fallback.
Docstring corrected.
existing
24
inconsistency
chat_sorcar_agent.py_build_extra_payload
The early history row never carried auto_commit_mode, and persistence._add_task maps absence to 0 while the schema default is 1, so every running or killed task showed "manual commit".
Toggle snapshotted once per run and written to both rows.
The lost-session latch was set only in the old socket's onclose; a wake-up while the dead socket was still CLOSING nulled that handler, so the replacement socket never reloaded the page.
Latch set inside connect() when replacing an authenticated socket.
Hand-rolled temp file with a fixed name next to the module's _atomic_write_text; two instances produced an empty flag.
Use _atomic_write_text.
test_audit0902_server_web_reset_flag_atomic.py
29
inconsistency
web_server.pyGET /
chat.html rendered once at start: the remote app's trick list froze while completions re-read the file per keystroke.
Render per request.
test_audit0902_server_web_html_tricks_fresh.py
30
redundancy
web_server.py five command handlers
Five copies of the field-sanitising / work-dir fallback block.
_cmd_str(), _cmd_work_dir().
test_audit0902_server_web_cmd_field_sanitizing.py
31
race
merge_flow.py_handle_worktree_action
State, agent and pending worktree resolved outside _state_lock; a concurrent closeTab or run let merge() run alongside the other path's disposal of the same worktree (2 disposals reproduced).
Resolution, guards and the is_merging claim form one locked section.
test_audit0902_server_core_merge_state_toctou.py
32
inconsistency
task_runner.py subtask metrics
Baselines captured before agent.run(), but _reset zeroes the counters, so the 2nd+ subtask persisted 0 tokens/cost.
Counters zeroed before each run; raw per-run values persisted.
close_tab did not strip ids like the other methods; two drifted copies of the entry-sanitising loop left blank titles from disk.
Shared _sanitize_entries(); close_tab strips.
test_audit0902_server_core_tab_registry.py
3.4 src/kiss/agents/vscode/
#
Category
Where
What was wrong
Fix
Test
34
inconsistency
media/main.js worktree bar
The daemon marks a deferred discard retryable so the bar's buttons are kept, but nothing re-enabled them: the bar the user was told to retry with was dead.
setActionBarBtnsDisabled(bar, false) on retryable results, foreground and background.
test/audit0902_vscode_main_retryable_bar.test.js
35
inconsistency
main.jsmarkTabDone, task_events
Terminal task_done carries no verdict but wrote lastTaskFailed=false, so a failed live task got a green dot while its replay got red; replays never reset a stale red dot.
Flag only raised by terminal events; reset before replay.
audit0902_vscode_main_tab_fail_dot.test.js
36
inconsistency
main.jssendReady
Reported activeTabId verbatim (possibly a content tab) while every sibling reported a chat tab; Git Commit then targeted a content tab.
chatTabIdForHost() shared by all callers.
audit0902_vscode_main_ready_chat_tab.test.js
37
redundancyinconsistency
main.jstask_done
Re-implemented doneLabelFor and measured a background tab's duration with the visible tab's clock.
Use doneLabelFor(); per-tab clock fallback.
audit0902_vscode_main_done_label.test.js
38
redundancy
media/main.css
60 duplicated lines across four overlays, four close buttons and two list panes.
await stop(); if (!running) start(): a voiceToggle off during the awaited stop was overridden and the microphone re-opened.
Queued start; a later stop() cancels it.
audit0902_vscode_ext_voice_lifecycle.test.js
40
race
src/voiceWake.tsstop
A child whose spawn failed never emits exit; stop() waited on it for ever and every later start() queued behind it.
Pid-less child settles the stop on error.
same file
41
race
src/UpdateChecker.jswriteCache
Fixed temp-file name: two windows truncated each other's temp; ~35% of reads saw an empty cache and re-hit PyPI.
Per-process temp name; unlink on failure.
audit0902_vscode_ext_update_cache_atomic.test.js
42
inconsistency
src/types.ts, WebviewNotifications.ts
Six message types relayed to the webview were missing from ToWebviewMessage; the poster used Record<string, unknown> and a cast.
Variants added; cast removed.
tsc --noEmit
43
race
SorcarSidebarView.tsrunUpdate
No single-flight guard: two clicks started two installers on the same checkout.
Guard (later replaced by a cross-process lock in the installer, see 4.4 and 4.5).
audit0902_vscode_ext_update_single_flight.test.js
44
race
src/userAssets.ts
Truncating writeFileSync after existsSync: the daemon's per-keystroke reader saw an empty MY_INJECTION.md 16,510 times in 300 seeds; two seeders mixed contents.
Stage + linkSync, EEXIST means the other seeder won (mirrors user_assets.py).
4. Findings from the gpt-5.6-sol reviews (41 fixed)
The reviewers read the diff and the surrounding code without editing anything. Findings were checked before dispatching a fix; items rejected as non-defects are in section 5.
4.1 Round 1, core (10)
Where
What the review found
Fix
kiss_agent.py text-only implicit finish
Returned success=False, is_continue=True, so RelentlessAgent resumed a text-only model until max_sub_sessions ran out.
Explicit outcome per path: text-only is terminal (success=True); stagnation unchanged.
kiss_agent.py_implicit_finish_allowed
Guard consulted before hook, unlike a real finish call.
Hook first; guard only if the hook accepted.
kiss_agent.py_reset
Two runs in the same second shared one trajectory filename.
Strictly increasing stamp (refined in round 2).
config.py
Wording still ambiguous about which DEFAULT_MAX_BUDGET.
Names the module explicitly.
model_info.py_read_my_models
Invalid UTF-8 in MY_MODELS.json crashed import kiss.core.models.model_info.
except (OSError, ValueError).
README.md
Embedding-model count stale after the catalog fix.
12 → 11.
model.py_cli_turn
Thinking bracket closed only for stall/interrupt; any other exception left it open.
Unconditional finally.
model.pywait_for_exit
Blocked up to 10 s after stdout EOF without checking the stop signal.
Polls in stop slices; raises KeyboardInterrupt on Stop.
model.py_write_prompt
Blocking buffered stdin.write() still pinned the writer thread and fd for a grandchild's lifetime.
Non-blocking fd, select-guarded os.write loop re-checking cancel, child exit and deadline; stdin closed in the writer's finally.
two audit tests
Wall-clock discriminators would flake under load.
Semantic assertions (__cause__, thread/fd state).
4.2 Round 1, agents/sorcar (8)
Where
What the review found
Fix
git_worktree.pycreate
Ignored save_owner_pid()'s result: with .git/config.lock held the worktree came back owner-less and a peer reclaim deleted it.
Tab disposal unregistered the state before preserving, so a fail-closed keep was invisible to the live-worktree exclusion; own-pid reclaim exemption had no in-process protection.
Claim, retire via retire_for_disposal(), unregister only when the claim was dropped; volatile in-process preserve claims honoured by load_preserve_marker.
sorcar
worktree_pool.py
A refill capturing the new generation could still publish while the sweep was running.
_discarding gate in the publication check.
server
task_runner.py outer setup catch
Interrupt before the inner try was labelled by hand, never acknowledged (re-injection into cancellation handling) and mislabelled shutdown as user stop.
Routes through _cancel_outcome.
server
_kiss_web_launcher.py, server.py
The embedded channel-agent server shared the canonical tabs.json; two registries overwrote each other.
VSCodeServer.use_private_tab_registry(); launcher uses a private file.
server
pinned-workdir test
Ordering flake (1/22).
A-side probe awaited before B's commands.
vscode
both install scripts
mkdir lock with stale-breaking rm -rf admitted 2 of 8 contenders; lock keyed on $KISS_HOME while the checkout follows $HOME.
perl flock on fd 9 at $HOME/.kiss/.update.lock, released by the kernel; fd closed on long-lived launches.
vscode
SorcarSidebarView.ts
Terminal-lifetime guard refused every later click until the terminal was closed.
Removed; the installer's lock decides.
vscode
types.ts
openSubagentTab.task_id and the fileContent variant missing.
Added; 13 real payloads type-check.
vscode
web_server.pyrunUpdate
Lock refusal exited 1 silently; the browser was told the update was installing.
_watch_update_exit reports non-zero exit (with the refusal line) to the requesting connection.
4.6 Found during final verification
Nine pre-existing test_cc_* files stubbed the CLI's Popen with a _FakeStdin lacking fileno(); after the non-blocking writer (4.1) the writer thread raised AttributeError, which pytest surfaced as PytestUnhandledThreadExceptionWarning in 8 of the 30 splits. The stubs now hand the writer a real open(os.devnull, "wb"); all 76 tests in those files pass with the warning promoted to an error.
5. Left alone deliberately
Where
Observation
Why no change
core/printer.py, json_printer.pyreset()
No production caller.
Public API used by many tests.
kiss_agent.pystep_count before _check_limits
Off by one in a saved trajectory after a budget error.
"Step in progress" semantics; RelentlessAgent's first-step heuristic depends on it.
Documented design; the window is closed by the finish guard.
SorcarTab.ts tips first run, restart-lock stale breaking
existsSync → write TOCTOU.
Effect is one extra tips panel or a rare lock hand-off; not reproducible without injected sleeps.
double PyPI fetch on activation; 0600 perms on a non-secret asset
Reviewer nits.
Harmless; no behaviour to test.
main.jsnew_tab handler
Reported dead.
Wrong: chat_sorcar_agent.py emits it.
tests/core/test_artifact_dir_and_step_limit.py
Fails only after test_kiss_agent.py in one process.
Pre-existing test-isolation defect (fails at HEAD with all changes stashed); outside the four audited trees.
6. Verification
Check
Result
uv run check --full (ruff, mypy, pyright, extension typecheck and lint)
All checks passed.
Full Python suite, 7,905 tests, 30 parallel splits
29 splits green. One failure, tests/agents/vscode/test_content_tab_file_links.py::test_code_link_opens_separate_tab_with_code: Playwright selector timeout under 30 concurrent browser-driving splits; 8/8 pass in isolation. The same class of load flake (test_remote_panels_match_extension) appeared in the previous session's run and also passes alone.
Nine repaired test_cc_* files with -W error::pytest.PytestUnhandledThreadExceptionWarning
76 passed.
JS suite, node test/run-all.js, 268 suites
268/268 suites passed (functional suites and V8 line-coverage gates; the gates require 100% of the fenced lines to execute).
New tests: src/kiss/tests/**/test_audit0902_*.py (45 files) and src/kiss/agents/vscode/test/audit0902_*.{test,coverage}.js (21 files). Nothing has been committed; all new files are staged with git add.