Repository navigation
fix: follow-up documents, launcher hardening, dependency advisories - #331
Open
dovvnloading wants to merge 7 commits into
Open
dovvnloading wants to merge 7 commits into
dovvnloading wants to merge 7 commits into
Conversation
A new turn read the whole transcript to build the model's history, which copied and JSON-decoded every reply's reasoning, sources and stats only to drop them; a fork read and decoded the whole source chat just to check that one message id was in it. DatabaseManager.load_messages(thread_id, columns=...) selects only the named fields (a fixed whitelist, never passed to SQL as given), decodes them the way load_chat does, and returns None for an unknown chat. chat_exists is a SELECT 1. Both repositories expose them as get_messages and chat_exists, covered by the shared contract tests. The new-turn history read now asks for the five fields history is built from, the fork check reads only ids, and rename asks chat_exists. load_chat shares the row decoding so the two reads cannot drift. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… PATH The WebView2 signature check (the gate before the bundled installer runs), the private-folder ACL and the dev-server tree kill started their tools by bare name, so whichever powershell.exe, icacls or taskkill came first on a user-modified PATH ran instead. core/win_paths.system32() resolves them under the directory GetSystemDirectoryW reports (%SystemRoot% / %windir% only as a fallback), with PowerShell's WindowsPowerShell\v1.0 subfolder, and falls back to the bare name only when that absolute path does not exist. Plan item: LP-12 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…log every failed exit InstanceLock.acquire() returned None for every failure -- an icacls error, a lock file that could not be opened, a secret or record that could not be written -- exactly as it did for "another Cortex holds the lock". The launch then went looking for a running instance that did not exist and exited 2. acquire() now returns None only for a contended lock (EACCES/EAGAIN/ EWOULDBLOCK/EDEADLK from the lock call, or from writing the locked byte) and raises InstanceLockError(reason) for anything else; _run_web records it under the stage "instance lock" and the dialog names the --data-dir remedy. The lock-file size is read with fstat instead of reading the byte, which fails under a held lock, and normalising writes go straight to the descriptor. Several failing exits (no record behind a held lock, a dead holder's lock that never frees, an unreadable handoff secret, a headless backend that stopped on its own) printed to a console a windowed build does not have and wrote no startup diagnostic, while the dialog said the log "could not be written". Every non-zero exit now goes through _fail(stage, message, code), which writes the diagnostic and prints; the dialog only mentions an unwritable log when a write was actually attempted. Plan item: LP-10 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dependabot reports a high-severity advisory against urllib3 2.7.0 in both locks, first patched in 2.8.0. Regenerated with the command in each lock's header plus --upgrade-package urllib3; nothing else moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dependabot reports one high, two moderate and three low advisories against undici 7.29.0 (pulled in by jsdom, test tooling only), first patched in 7.29.1. `npm update undici` takes it to 7.30.1, inside jsdom's range. npm 11 also dropped the optional @emnapi/runtime entry that CI's npm 10 needs; it is restored unchanged, so undici is the only package that moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
readLauncherCredentials() still honoured ?bootstrap= and ?handoff= in the query string, although the launcher has only ever sent them in the fragment (main.py _desktop_url), which never reaches a server, a request line or a log. Nothing in production used the query branches -- only the Playwright specs and the screenshot scripts did. The query is no longer read (it is still scrubbed from the address bar), and those specs and scripts now open /#bootstrap=... like the launcher does. Plan item: LP-02 (remainder) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Summarise this" with a document, then "now list its section headings": only attachment metadata is stored with a message, so the follow-up reached the model with no document text at all, and it invented the answer. When a turn attaches nothing, the documents attached to the user's most recent message are resolved again, through the same owner and integrity checks, and sent marked as coming from the previous message, under their own heading. They go through the attachment budget that scales with the context window like any document. Images stay with their own turn, only the immediately previous message lends documents, and a turn that attaches its own files gets only those. A document deleted or expired since is left out (a count is logged, never a name or text) and the turn proceeds. Nothing is persisted on the follow-up; regenerating its reply brings the same documents back. To have the previous turn's attachments before resolution, the new turn's one partial transcript read moves from prepare() to admission. The user turn is still appended guarded by the admitted revision, so a write landing after that read fails the append as before. A brand-new chat no longer makes a read at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 17, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three groups of verified fixes in one pull request, at the owner's request: one wait and one merge, instead of one pull request per group. It supersedes #329 and #330 and adds the dependency advisory fixes. Each group was first pushed on its own branch, and each passed the full pre-push hook there (all 11 checks); the combined branch runs it again.
A. Follow-up documents (from #329)
Problem
Root cause
get_chat(everything) andget_chat_overview(no messages).Change
Two commits.
perf(persistence): read only the message fields a caller uses.DatabaseManager.load_messages(thread_id, columns=...)selects only the named fields from a fixed whitelist; a name outside it is aValueErrorand never reaches SQL. It decodes them asload_chatdoes, in one read transaction, and returnsNonefor an unknown chat.chat_existsis aSELECT 1.get_messagesandchat_exists, covered by the shared contract suite (fix(persistence): harden chat and memory storage #319).HISTORY_MESSAGE_FIELDS); the fork check reads ids; rename askschat_exists.load_chatshares the row decoding, so the two reads cannot drift.fix(chat): give a follow-up the documents of the message before it.prepare()to admission, so the previous turn's attachments are known before resolution. The user turn is still appended withexpected_revisionset to the admitted revision. Since fix(persistence): harden chat and memory storage #319 any write moves the revision, so a write landing after the read fails the append, as before.Not included: the third commit of the original branch (a sticky per-thread code contract, BE-27). It touches the code-execution prompt and needs its own review.
Compatibility and rollback
ChatRepositorygains two methods. The only implementations are the SQLite adapter and the in-memory double, both updated.Checks
Windows, Python 3.14 unless stated.
python -m pytest -q tests/test_chat_attachments.py tests/test_prompt_shape.py tests/test_event_loop_hygiene.py tests/test_chat_repository_contract.py tests/test_chat_correctness.py tests/test_generation_harness_controls.pygives 284 passed, 19 subtests.py -3.12) on the four changed test files: 195 passed.test_chat_attachments.pyandtest_prompt_shape.pycopied onto an export oforigin/main(2972c7a) give 10 failed, 78 passed. The 10 are the new follow-up tests, including deleted/expired documents, images not resent, only the previous message, and the budget at two window sizes.test_event_loop_hygiene.pyandtest_chat_repository_contract.pycannot be collected there, because they use the new repository methods.scripts/check.ps1quick tier): all 11 checks passed in 416.5 s (backend 255.7 s, vitest 91.8 s).Security, concurrency, data loss
stale_revision.Limits
B. Launcher hardening (from #330)
Problem
Three launcher defects:
powershell.exe,icaclsandtaskkillby bare name.PATHran instead of Windows' own.InstanceLock.acquire()returnedNonefor every failure, exactly as for "another Cortex holds the lock". That included an icacls error, a lock file that could not be opened, and a secret or record that could not be written.readLauncherCredentials()still honoured?bootstrap=and?handoff=in the query string.main.py_desktop_url), which never reaches a server, request line or log. Only Playwright specs and screenshot scripts used the query form.Change
One commit each.
core/win_paths.system32(name)resolves under the directoryGetSystemDirectoryWreports, with%SystemRoot%/%windir%only as a fallback. PowerShell is looked up inWindowsPowerShell\v1.0. It falls back to the bare name only when that absolute path does not exist. It is used at all three call sites.acquire()returnsNoneonly for a contended lock. That meansEACCES/EAGAIN/EWOULDBLOCK/EDEADLKfrom the lock call or from writing the locked byte.InstanceLockError(reason). Its message is fixed text plus the OS error's class name, never a path or secret._run_webrecords it under the stage "instance lock", and the dialog names the data-folder remedy.fstat(reading a locked byte fails), and normalising writes go straight to the descriptor._fail(stage, message, code), which writes the diagnostic. The dialog mentions an unwritable log only when a write was attempted./#bootstrap=...as the launcher does.Not included from the original branch, because they add features rather than fix defects: remembering the window's size and position, and asking before closing during a reply.
Compatibility and rollback
Checks
Windows, Python 3.14 unless stated.
python -m pytest -q tests/test_launcher.py tests/test_win_paths.pygives 222 passed. Python 3.12 (py -3.12): 222 passed.npx vitest run src/app/App.test.tsxgives 39 passed.origin/main. Instead, the old behaviour was put back inside the new code:_contended()made to always returnTrue(every failure treated as contention), andsystem32()made to skip the system directory. Result: 6 failed, 216 passed, includingtest_instance_lock_distinguishes_contention_from_setup_failure, the PowerShell, icacls and taskkill path tests, and the WebView2 check.scripts/check.ps1quick tier): all 11 checks passed in 637 s (backend 450.8 s, vitest 99 s).Security
PATHdependency from the installer's signature gate.Limits
C. Dependency advisories
Problem
Dependabot reports open advisories on two packages:
urllib32.7.0, high severity, in both Python locks. First patched in 2.8.0.undici7.29.0, pulled in by jsdom (test tooling only): one high, two moderate and three low. First patched in 7.29.1.Change
--upgrade-package urllib3. Only urllib3 moves, to 2.8.0, with its new hashes.npm update undicitakes it to 7.30.1, inside jsdom's range.@emnapi/runtimeentry that CI's npm 10 needs. It is restored unchanged, so undici is the only package that moves inpackage-lock.json.Checks
package-lock.json, version and integrity only.npm ciwith the new lock, thennpx vitest run src/api: 48 passed.Combined branch checks
scripts/check.ps1quick tier) on the combined branch: all 11 checks passed in 504.4 s.origin/main(1ae4975) without conflicts.Plan item: BE-34, BE-02, LP-12, LP-10, LP-02 (remainder), plus the urllib3 and undici advisories
🤖 Generated with Claude Code