kiss/wt-1788470827-0b6a10f8, 2026-09-03This audit hunted two defect classes only — race conditions (any concurrent interleaving that produces wrong behavior: torn reads and writes, check-then-act windows, lost updates, missing locks, async-exception hazards) and redundancies (duplicated logic that has drifted or can drift, duplicate state, dead code). Inconsistencies were explicitly out of scope per the task. A previous audit of the same four directories (2026-09-02, 86 defects) is already merged into this branch's base; this pass therefore concentrated extra scrutiny on the roughly six commits this branch adds on top of it — a rewritten daemon client with stop-on-timeout, a new run_agent timeout, WebUse renderer-hang hardening, installer lock/marker repairs, and a refreshed model catalog — while still sweeping every file end to end.
52defects fixed 36races 12redundancies 4test/lint gaps 34new test files ~60suspicions investigated & rejected 8,047Python tests green* 281/281JS suites green
Ten audit agents owned disjoint file partitions (core base, core models, sorcar agents, sorcar infrastructure ×2, server core, server web, vscode extension, vscode installer, vscode webview). Four gpt-5.6-sol reviewers then re-derived every claimed fix from the diff, hunted for missed twin sites and introduced bugs, and re-checked the rejected suspicions; a fifth gpt-5.6-sol agent triaged a Python 3.14 segfault seen once during review (verdict: not reproducible, not attributable to these changes; the leaky test fixture it exposed was fixed). Four fix agents closed all 17 review findings, again test-first. The gpt-5.6-sol spend stayed far below the 50% budget cap.
| # | Class | Where | Defect and fix |
|---|---|---|---|
| 1 | race | core/vscode_config.py | source_shell_env applied RC-sourced keys to os.environ/config without _config_lock; a concurrent API-key save was silently reverted (28/120 stale in the probe). Now holds the lock from RC snapshot through refresh. |
| 2 | redundancy | core/kiss_agent.py | The agentic loop kept an inline, drifted copy of _print_result (empty results rendered differently per path). All three run modes now share one implementation. |
| 3 | race | core/models/model.py | CLI stdin writer guarded writes with select.select, which raises for any fd ≥ 1024; the handler swallowed it and closed stdin after zero bytes — in a busy daemon every cc/*/codex/* turn silently got an empty prompt. (Final form of the fix: finding 36.) |
| 4 | redundancy | core/models/MODEL_INFO.json | The catalog refresh resurrected three Codex models verified to fail on ChatGPT subscriptions, because the exclusion knowledge lived only in a test. Entries removed. (Root cause: finding 33.) |
| 5 | race | sorcar/sorcar_agent.py, relentless_agent.py | Cumulative usage counters (budget_used, tokens, steps) were read-modify-written from the agent thread and server reclaim threads with no lock — the failing test banked only 22,295 of 24,000 increments. One per-agent _usage_lock now guards every writer. |
| 6 | race | sorcar/sorcar_agent.py | In the fan-out engine, a worker publishing its final usage slot between abandoned-registration and totals summation got its spend banked twice. Registration and summation now happen under one hold of sub_usage_lock. |
| 7 | redundancy | sorcar/worktree_sorcar_agent.py | Byte-identical exception-wrapping blocks on the direct-fallback and worktree paths merged into one. |
| 8 | redundancy | sorcar/sorcar_agent.py | Two hand-rolled readers of the live executor's spend (an easy field to copy wrong) extracted into _executor_usage(). |
| 9 | race | sorcar/daemon_client.py | stop_on_timeout discarded a natural completion: a task finishing just as the deadline expired still surfaced as "stopped", losing the finished work. The terminal-status wait now returns a stored success: true result (sound: every daemon stop/cancel/failure path broadcasts success: false). (Hardened in finding 38.) |
| 10 | redundancy | sorcar/daemon_client.py | Duplicated oversize-frame error and run-token-guarded stop frame extracted into _frame_limit_error() / _send_stop() — drift there would silently lose the tab-reuse guard. |
| 11 | redundancy | sorcar/web_use_tool.py | The "live killable foreign pid" predicate existed twice, once inverted — a drift hazard whose failure mode is signalling the wrong process. Extracted _killable_live_pid(). |
| 12 | race | sorcar/worktree_pool.py | Overlapping discard_all() calls defeated the _discarding boolean: the first caller cleared it while the second still swept, letting a refill publish a spare behind a teardown. Replaced with an _active_discards counter decremented in finally. |
| 13 | race | sorcar/docker_manager.py | Five methods read self.container more than once without the lifecycle lock; racing close() turned orderly refusals into AttributeError, and killed the timed-out-exec reaper thread. One snapshot per method; the reaper treats a closed container as nothing to kill. |
| 14 | redundancy | sorcar/docker_manager.py | Token-tagged exec_create duplicated across both Bash paths extracted into _tagged_exec_create(). |
| 15 | redundancy | sorcar/persistence.py | Duplicated thread-local connection teardown extracted into _close_cached_thread_conn(). |
| 16 | race | server/task_runner.py | A stop arriving after the worker thread was registered but before thread.start() armed no force-stop watchdog (the liveness check had drifted from the ident-aware helper) — the run then executed untrusted setup forever. Ident-aware liveness plus a watchdog that waits for thread start under STATE_LOCK. |
| 17 | race | server/task_runner.py | The untrusted-code loaders wrapped BaseException — including the stop watchdog's injected KeyboardInterrupt — into task errors, so a stop landing in a getter was reported as a failure and never acknowledged. New _stop_interrupt_wrapped() routes a genuine stop through _cancel_outcome. |
| 18 | race | server/server.py | History-resume subscribed and broadcast running=true after releasing the state lock; a task ending in that window left viewer tabs spinning forever. The attach now re-verifies the state object's liveness after the optimistic broadcast. (Extended in finding 42.) |
| 19 | race | vscode/src/SorcarSidebarView.ts | Three interleavings used voiceWake.running (true while a stopped listener is still dying) as a stand-in for user intent, turning the microphone ON against the user's wishes. A _voiceEnabled intent flag now gates sensitivity restarts and hide/show suspension, cleared on dispose. |
| 20 | race | vscode/src/SorcarTab.ts | The one-time tips popup was claimed with exists-then-write; two windows activating together both won. Atomic writeFileSync(..., {flag:'wx'}). |
| 21 | race | scripts/install.sh | A skip-worktree pin left by an old installer bricked the curl bootstrap forever (pull, checkout, stash and reset all fail). The bootstrap now clears the pin first. |
| 22 | redundancy | vscode/ | A committed rescued-VSIX binary artifact (kiss-sorcar.vsix.kiss-rescued-…) removed from git. (Root cause: findings 34/39.) |
| 23 | redundancy | install.sh | update_repo ran guard_vsix_tracking twice (silenced probe plus loud re-run); now one captured run. |
| 24 | race | vscode/src/DependencyInstaller.ts | Truncate-then-write of the executable ~/.local/bin/sorcar let a shell exec an empty wrapper. New writeFileAtomicSync (unique temp + rename). |
| 25 | race | vscode/src/DependencyInstaller.ts | Same torn-write hazard for the user's shell rc (a terminal opened mid-write sourced a truncated .bashrc). Same atomic-write fix, symlinks and modes preserved. |
| 26 | race | vscode/src/DependencyInstaller.ts | Two VS Code windows both prompted for the remote password and the saves raced last-write-wins. A pid+token lock file now elects one prompter. (Hardened in finding 46.) |
| 27–30 | race | vscode/media/*.js | Four untracked flash/revert setTimeout sites (panel copy button, tips copy label, share button, remote-URL copy) let an earlier click's stale timer cut short or corrupt a later click's feedback. Per-element tracked timers, cleared before re-arm. |
| 31 | race | vscode/media/main.js | The resetFlashTimer opt-in flag in wireCopyButton left sidebar history/frequent copy buttons racy while others were fixed; flag removed, every caller restarts the timer. |
| 32 | redundancy | vscode/media/main.js | Two identical 9-line composer-reset blocks extracted into resetComposerAfterSend(). |
| # | Class | Where | Defect and fix |
|---|---|---|---|
| 33 | redundancy | scripts/update_models.py | Which Codex slugs are subscription-incompatible was known only to a guard test, so a catalog refresh resurrected the bad entries. A single SUBSCRIPTION_INCOMPATIBLE_CODEX_SLUGS set is now consulted by both the candidate-adder and the deprecation scan, and the guard test imports it. |
| 34 | race | sorcar/git_worktree.py | Worktree-rescue collision copies were named app.log.kiss-rescued-<ns> — no longer matched by *.log-style ignore rules, so the next auto-commit committed the rescue copy (exactly how the binary VSIX landed in git). The marker now goes before the extension. (Completed by finding 39.) |
| 35 | race | tests/core/test_artifact_dir_and_step_limit.py | Pre-existing test-order dependency: a bare *_{id}_*.yaml glob matched same-id trajectories from earlier tests in the process. Scoped to the test's own trajectory name. |
The reviewers verified every fan-out fix against the diff (their verdict sections confirm items 1–32 individually) and found the following gaps, all closed test-first:
| # | Severity | Where | Finding and fix |
|---|---|---|---|
| 36 | HIGH | core/models/model.py | The select.poll() replacement (item 3) does not exist on native Windows — the writer thread died and every CLI turn silently sent an empty prompt there. The readiness primitive is gone entirely: direct non-blocking os.write, retrying on BlockingIOError with a cancellable wait. The fd-pressure test now also runs with the select APIs removed, and the test module collects on Windows. |
| 37 | LOW | tests/scripts/ | The exclusion tests were helper-level; a real pipeline test now seeds a temp MODEL_INFO.json and runs deprecation → compute_changes → apply_updates_to_file twice, proving removal and non-resurrection through the written JSON. |
| 38 | HIGH | sorcar/daemon_client.py | Item 9's grace-expiry branch returned a stored success result without terminal status — but persistence/auto-commit/worktree cleanup still run after the result is emitted, so the task could outlive its workspace reservation. Without terminal running=false the client now always raises StopUnconfirmedTimeoutError; the sound terminal-status return stays. |
| 39 | MED | sorcar/git_worktree.py | Item 34's renaming still leaked compound suffixes (*.tar.gz) and exact-name rules (.env). The rescue now installs an idempotent *.kiss-rescued-* pattern in the destination repo's info/exclude (via git rev-parse --git-path, worktree/submodule-safe) before landing, and verifies siblings with git check-ignore. |
| 40 | HIGH | server/web_server.py, commands.py | Graceful shutdown crashed with RuntimeError: cannot join thread before it is started in the same pre-start window as item 16, and an in-flight run could start after the shutdown sweep. _cmd_run now takes the start decision atomically under STATE_LOCK (a swept run is routed through terminal cancellation before any user setup), and shutdown shares the watchdog's wait_for_thread_start() primitive. |
| 41 | MED | server/task_runner.py | The watchdog's 30 s start-wait could abandon an accepted stop if registry I/O stalled longer. The finding-40 handshake honors the stop in _cmd_run itself, making the deadline a harmless backstop — covered by a real two-client UDS test. |
| 42 | MED | server/server.py | The replay correction could emit a stale running=true/running=false pair that killed a newer run's spinner on the same viewer tab. Both broadcasts now happen under one state-lock hold with a current-owner check; _broadcast_status_end_to_viewers aligned. |
| 43 | MED | server/ | An attaching viewer could miss the terminal result entirely (early setup failures were addressed only to the launcher tab and never recorded). Setup results are now stamped with the run's task id and recorded; _replay_session re-snapshots and delivers the terminal result before the corrective status. One residual window (recording already dropped while the events table lags) is documented precisely in the fix report. |
| 44 | MED | server/autocomplete.py | A superseded background refresh could emit a stale old-workspace file list last (the server-web report's rejection of this was wrong). Ranking now happens outside the lock; the request token is re-verified under the lock at emission and removed only after emitting. |
| 45 | LOW | tests/server/ | The UDS test fixture leaked pending handler tasks; it now drains them before stopping the loop. Real two-client UDS tests added for the pre-start stop and shutdown paths. |
| 46 | MED | vscode/src/DependencyInstaller.ts | Item 26's waiter only polled for lock-file disappearance: a crashed prompter stuck it for 10 minutes, while a live 10-minute prompt got evicted (duplicate prompts). Prompt-specific pid+token protocol: waiters detect dead owners and take over in under a second, live holders are never age-evicted, and a token-stamped terminal outcome distinguishes a deliberate Esc from a crash. |
| 47 | MED | vscode/src/DependencyInstaller.ts | The atomic writer destroyed dangling dotfile symlinks (realpath throws, rename replaced the link). It now walks the link chain with readlinkSync and creates the missing referent. |
| 48 | LOW | vscode/src/SorcarTab.ts | Every window seeing the update marker independently reset the tips claim, so two windows could each reset-then-claim. A per-update-version wx reset-claim elects exactly one resetter. |
| 49 | LOW | vscode/media/tips.js | Out-of-order async clipboard completions could overwrite the latest copy result with a stale failure. Per-button copy generation; only the newest completion updates the label. |
| 50 | LOW | vscode/src/DependencyInstaller.ts | The temp file wasn't cleaned up when the write itself failed (reproduced with a real ulimit -f file-size limit). Whole temp lifecycle under one try/finally. |
| 51 | LOW | vscode/test/ | The installer fixes had no coverage gate; a new V8 gate covers all fenced audit0903 regions of DependencyInstaller.js at 164/164 lines. |
| 52 | LOW | vscode/src/DependencyInstaller.ts | One prettier violation introduced by the fixes; resolved, scoped eslint clean. |
test_audit0903_*) under src/kiss/tests/: real UNIX-socket daemons and clients, real git repositories and worktrees, real Docker containers, real installer runs in sandboxed $HOMEs, real child processes under fd pressure, real threads hammering shared counters. No mocks, patches, or fakes of the code under test anywhere.audit0903_*) under src/kiss/agents/vscode/test/: jsdom DOMs driving the real webview code, real racing node processes for the cross-window locks, V8 line-coverage gates at 100% of every fenced fix region.uv run check --full (ruff, mypy, pyright, extension typecheck and lint): all checks passed.node test/run-all.js): 281/281 suites passed.test_update_build_npm_ci_flags_block_install_scripts fails in this sandbox because a plain npm ci (the test's self-check phase, in a file this audit never touched) stalls in npm's audit network step; the flags actually under test behave correctly. Pre-existing environment limitation, left alone.src/kiss/tests/server folder was additionally run at once after the server fixes: 2,311 passed, 0 failed, and the Python 3.14 segfault seen once during review did not recur (a dedicated gpt-5.6-sol triage found it non-reproducible and not attributable to these changes).About sixty suspicions were investigated and rejected with recorded evidence in the per-partition reports (tmp/audit2-report-*.md, tmp/review-*.md, tmp/fix-report-*.md): among them the daemon client's frame-assembly index (proven correct for every chunking), the abort-cascade vs tab reuse (guarded by the run token, verified server-side), pool spare consumption vs reclaim (protected by repo_lock), cron tick/run-now overlap (documented tradeoff), voice-wake double-start (unreachable through per-connection serialization), and the CPython-level impossibility of closing sub-bytecode async-exception windows. Also left alone: one residual replay window documented in finding 43, and the sandbox-only npm audit stall above.
claude-fable-5. Review and debugging model: gpt-5.6-sol (well under the 50% budget cap).