Skip to content

Fix three bugs found in a pre-release audit#534

Merged
FuJacob merged 1 commit into
mainfrom
fix/pre-release-audit-bugs
Jun 2, 2026
Merged

Fix three bugs found in a pre-release audit#534
FuJacob merged 1 commit into
mainfrom
fix/pre-release-audit-bugs

Conversation

@FuJacob
Copy link
Copy Markdown
Owner

@FuJacob FuJacob commented Jun 2, 2026

Summary

A pre-release bug audit (fanned out across the runtime, coordinator, insertion, support helpers, and settings). Three confirmed bugs fixed; two findings deliberately left alone and explained below. No new features.

Fixed

  1. Multi-line completions were silently suppressed (real shipping toggle broken). cotabbyMultiLineEnabled is a user-facing toggle, but InsertionSafetyGate rejected every control char < 0x20, including the line feed the normalizer keeps in multi-line mode. So multi-line completions always normalized to empty and never appeared. Now 0x0A passes; tab/other C0/U+FFFD are still rejected. Tests added.
  2. Typing through a suggestion regenerated against pre-keystroke text in Chromium. completeActiveSuggestion always called schedulePrediction(), but the typed-through-exhaustion caller runs from the synchronous input tap before the host publishes the keystroke. The next suggestion was built against stale text and looked like the typed chars were ignored (the exact Chromium AX-publish race that schedulePredictionAfterHostPublishDelay exists to fix). That path now awaits host publish; the reconcile-path caller, where AX has settled, still schedules immediately.
  3. The opt-in paste path could clobber or leak the clipboard. Restore ran unconditionally after a fixed delay (a copy during the window was overwritten), and overlapping pastes snapshotted each other's injected text (leaving a completion stuck on the clipboard). Restore is now guarded by changeCount (only restore if our completion is still there) and overlapping pastes coalesce onto one saved clipboard with a single rescheduled restore.

Deliberately not fixed (flagged for later, with reasons)

  • Fill-in-middle is currently inert. FIM markers are control tokens that detokenize to empty, so marker detection never matches and the runtime always falls back to the forward prompt. This is safe (no crash, no wrong output). "Fixing" it would activate an unvalidated feature and expose a second bug (stray BOS tokens injected mid-prompt), which is the opposite of a stability release. Needs an on-device fix later (detect markers by tokenizing with parse_special, and tokenize FIM prefix/suffix without BOS).
  • Beam KV-desync on a failed trimKV. Only reachable on the non-default beam path (we ship greedy, width 1) and only when a rare KV removal fails. Low severity, delicate code; left for a focused follow-up.

Validation

xcodebuild ... test ... -only-testing:CotabbyTests/InsertionSafetyGateTests
  -only-testing:CotabbyTests/SuggestionTextNormalizerTests
  -only-testing:CotabbyTests/LlamaSuggestionEngineCancellationTests
# ** TEST SUCCEEDED **  (34 tests, 0 failures)
swiftlint --strict   # exit 0

The coordinator and paste fixes live in CGEvent / NSPasteboard / @mainactor code the repo intentionally does not unit-test; both were verified by tracing the paths. Paste is default-off, so fix 3 affects only opt-in users.

Linked issues

None. Pre-release hardening.

Risk / rollout notes

  • Fix 1 changes shipped behavior for multi-line mode (it now actually shows multi-line completions). Single-line mode is unaffected (the normalizer strips newlines before the gate there).
  • Fix 2 touches the acceptance hot path; the change is one routing flag, default false preserves the reconcile path.
  • Fix 3 is contained to the opt-in paste path (default off).

Greptile Summary

This PR fixes three pre-release bugs: multi-line completions were silently rejected by InsertionSafetyGate because \n was caught by the C0 control-character check, the typed-through-exhaustion path scheduled the next prediction against pre-keystroke AX text in Chromium, and the opt-in paste path could clobber or leak the clipboard when pastes overlapped or the user copied during the restore window.

  • Fix 1 (InsertionSafetyGate): Exempts 0x0A from the C0 rejection so \n inside a multi-line completion passes the gate; all other control characters and U+FFFD are still rejected. The normalizer already strips \r before reaching the gate, so Windows line endings cannot bypass the fix.
  • Fix 2 (SuggestionCoordinator+Acceptance): Routes the typed-match-exhausted caller through schedulePredictionAfterHostPublishDelay() via a new awaitHostPublish parameter (default false); all existing callers are unaffected.
  • Fix 3 (SuggestionInserter): Coalesces overlapping pastes onto one saved clipboard, guards the async restore with a changeCount equality check, and cancels/reschedules the restore on each subsequent paste.

Confidence Score: 4/5

Safe to merge. All three bug fixes are well-reasoned and the changed paths are narrow: the gate fix only affects multi-line mode, the coordinator change is one routing flag with a safe default, and the paste fix is confined to a default-off opt-in path guarded by @mainactor serial execution.

The three fixes are individually correct and touch isolated paths. The only findings are a readability nit on the Swift condition-list form in InsertionSafetyGate and a missing test asserting that
remains rejected — both editorial rather than functional.

InsertionSafetyGate.swift has the minor readability issue on line 31; InsertionSafetyGateTests.swift is missing the
-still-rejected test case.

Important Files Changed

Filename Overview
Cotabby/Support/InsertionSafetyGate.swift Exempts 0x0A (line feed) from the C0 control-character rejection so multi-line completions can reach ghost text; logic correct, but comma vs && readability nit on line 31.
Cotabby/App/Coordinators/SuggestionCoordinator+Acceptance.swift Adds awaitHostPublish: Bool = false to completeActiveSuggestion and routes typed-match-exhausted through schedulePredictionAfterHostPublishDelay(); all existing callers unaffected.
Cotabby/Services/Suggestion/SuggestionInserter.swift Adds pendingPasteboardRestore/savedClipboardForRestore state to coalesce overlapping pastes and guard the async restore with changeCount equality; logic sound given @mainactor serial execution.
CotabbyTests/InsertionSafetyGateTests.swift Adds test_multiLineContent_isSafe and test_newlineOnly_isUnsafe — both correct — but missing explicit test that \r remains rejected after the 0x0A exemption.

Fix All in Codex Fix All in Claude Code

Reviews (1): Last reviewed commit: "Fix three bugs found in a pre-release au..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

1. Multi-line completions were silently suppressed. InsertionSafetyGate rejected
   any control character below 0x20, including the line feed the normalizer keeps
   in multi-line mode, so every multi-line completion normalized to empty and was
   never shown. Exempt 0x0A (newline); other control chars are still rejected.

2. Typing through a suggestion regenerated against pre-keystroke text in Chromium.
   completeActiveSuggestion always scheduled the next prediction immediately, but
   the typed-through-exhaustion path runs from the synchronous input tap before the
   host publishes the keystroke, so the new suggestion was built against stale text
   (the documented Chromium AX-publish race). It now waits for host publish on that
   path; the reconcile-path caller, where AX has settled, still schedules at once.

3. The opt-in paste path could clobber or leak the user's clipboard. The restore
   ran unconditionally after a fixed delay, so a copy during the window was
   overwritten, and two pastes within the window snapshotted each other's injected
   text and left a completion stuck on the clipboard. The restore now only runs if
   our completion is still on the clipboard (change-count guard), and overlapping
   pastes coalesce onto a single saved clipboard and reschedule the one restore.
// C0 control range and DEL. A line feed is legitimate content in a multi-line completion
// (the normalizer keeps newlines when multi-line mode is on), so it must pass; any other
// control character (an interior tab, a stray escape) is corruption, not content.
if scalar.value != 0x0A, scalar.value < 0x20 || scalar.value == 0x7F {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The comma-separated condition mixes Swift's condition-list && semantics with an infix || inside the second clause. The logic is correct, but a reader who doesn't notice that the comma binds looser than || could misread it as (scalar.value != 0x0A, scalar.value < 0x20) || scalar.value == 0x7F. Using && with explicit parentheses makes the grouping unambiguous.

Suggested change
if scalar.value != 0x0A, scalar.value < 0x20 || scalar.value == 0x7F {
if scalar.value != 0x0A && (scalar.value < 0x20 || scalar.value == 0x7F) {

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Claude Code

Comment on lines +39 to 43
func test_newlineOnly_isUnsafe() {
// Newlines are whitespace; a newline-only completion is still nothing worth inserting.
XCTAssertFalse(InsertionSafetyGate.isSafeToInsert("\n\n"))
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The new tests document that now passes and that a newline-only string still fails, but there is no explicit test showing that (0x0D) is still rejected. is stripped by SuggestionTextNormalizer before it can reach the gate in production, but a gate-level test would make the 0x0A-only exemption self-documenting and guard against future normalizer changes that might pass through.

Suggested change
func test_newlineOnly_isUnsafe() {
// Newlines are whitespace; a newline-only completion is still nothing worth inserting.
XCTAssertFalse(InsertionSafetyGate.isSafeToInsert("\n\n"))
}
}
func test_newlineOnly_isUnsafe() {
// Newlines are whitespace; a newline-only completion is still nothing worth inserting.
XCTAssertFalse(InsertionSafetyGate.isSafeToInsert("\n\n"))
}
func test_carriageReturn_isUnsafe() {
// Only 0x0A is exempted; \r (0x0D) is still a rejected C0 control character.
XCTAssertFalse(InsertionSafetyGate.isSafeToInsert("a\rb"))
}
}

Fix in Codex Fix in Claude Code

@FuJacob FuJacob merged commit 16e6063 into main Jun 2, 2026
4 checks passed
@FuJacob FuJacob deleted the fix/pre-release-audit-bugs branch June 2, 2026 05:42
@FuJacob FuJacob mentioned this pull request Jun 2, 2026
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant