You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
validate() only guards against the model adding text:
if !allowsExpansion && output.count >max(sourceCount *2, sourceCount +200){throwSmartCleanupError.invalidOutput("unexpectedly expanded the transcript")}
Nothing guards against it removing text. When the on-device model declines to reproduce part of a transcript, the truncated result passes validation and is reported as a success, so the user silently gets words they never said — or loses words they did.
Real entries from PipelineHistory on 1.1.8 (macOS 26, Apple silicon), all logged as Smart on-device cleanup succeeded:
Spoken
Pasted
What the fuck?
What
What the fuck?
I think we should ship this.
It actually seems really good, but can you check...
same text wrapped in a ``` fence
The first two are the notable ones: SystemLanguageModel is already constructed with guardrails: .permissiveContentTransformations, but the model still truncates at profanity or paraphrases around it, despite the system prompt's explicit instruction to Preserve language, names, technical identifiers, paths, flags, URLs, and profanity. Because the output is shorter than the input, nothing caught it.
Change
Three additional checks in validate(), all gated on !allowsExpansion so selection transforms (shorten/reword) are unaffected:
Truncation — reject when the output loses more than half the transcript.
Must-preserve terms — reject when profanity present in the source is absent from the output, enforcing the contract the system prompt already states.
Block markdown — reject a leading ```, #, or > that the source did not have, unless the speaker asked for a code block/heading/quote.
Bullets and numbered lists are deliberately not rejected — auto-list formatting seems desirable, and the prompt already permits it when requested.
A rejection is not a failure: it falls through to deterministicCleanup, which reproduces the speaker's words verbatim. On the samples above, that path already produced correct output.
validate changed from private static to static so tests can reach it, matching commandPrompt and friends.
Tests
New Tests/SmartCleanupValidationTests.swift, registered in the runner and the Makefile, covering the logged regressions plus the cases that must keep passing: filler removal, self-corrections, punctuation fixes, auto bullets/numbered lists, preserved profanity, and allowsExpansion transforms.
make test passes.
Notes
The threshold for check 1 is deliberately loose (50%) to avoid rejecting legitimate filler removal and self-corrections. It therefore does not catch smaller losses — e.g. This seems really fast. I think I might switch to this. → - I think I might switch to this still passes. Tightening it safely probably needs filler-aware content-word retention rather than a character ratio; happy to follow up if you want that.
The term list in mustPreserveTerms is English-only and intentionally short. Easy to extend, though a broader list raises the cost of a false rejection (a needlessly basic-cleaned transcript).
Reviewed this and built + ran the suite on macOS 26.5.2 (Apple silicon) — make test passes, including the new SmartCleanupValidationTests.
Verdict: good to merge. The approach is right: the three new checks are all gated on !allowsExpansion, and a rejection isn't a hard failure — it falls through to deterministicCleanup, so the worst case is a basic-cleaned transcript with the speaker's exact words rather than a silently truncated or censored one. That's the correct trade for a silent-data-loss bug. Tests cover both the regressions and the must-keep-passing cases (filler removal, self-corrections, auto lists, preserved profanity, selection transforms).
So Output is including JSON and tags like "cleaned_text". #14 needs a complementary fix at the normalizeCommandOutput layer (I've got one prepped — see the issue). The two compose cleanly: normalize runs before validate, so once the wrapper is unwrapped the fenced-JSON case yields the smart result and passes validation instead of falling back. No conflict with this PR beyond a trivial Makefile/runner line.
# prefix is broad. The heading check keys on a bare #, so a transcript the model renders as #YOLO (hashtag, not heading) would also be rejected. Fine given the safe fallback, just noting it's not strictly "heading."
English-only must-preserve list — you already called this out. Agree it's the right scope for now; a broader list raises the cost of a false rejection.
Nice catch on the guardrails: .permissiveContentTransformations interaction — the model truncating at profanity despite that setting is exactly the kind of thing that's invisible without reading PipelineHistory.
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
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.
Problem
validate()only guards against the model adding text:Nothing guards against it removing text. When the on-device model declines to reproduce part of a transcript, the truncated result passes validation and is reported as a success, so the user silently gets words they never said — or loses words they did.
Real entries from
PipelineHistoryon 1.1.8 (macOS 26, Apple silicon), all logged asSmart on-device cleanup succeeded:What the fuck?WhatWhat the fuck?I think we should ship this.It actually seems really good, but can you check...The first two are the notable ones:
SystemLanguageModelis already constructed withguardrails: .permissiveContentTransformations, but the model still truncates at profanity or paraphrases around it, despite the system prompt's explicit instruction toPreserve language, names, technical identifiers, paths, flags, URLs, and profanity.Because the output is shorter than the input, nothing caught it.Change
Three additional checks in
validate(), all gated on!allowsExpansionso selection transforms (shorten/reword) are unaffected:```,#, or>that the source did not have, unless the speaker asked for a code block/heading/quote.Bullets and numbered lists are deliberately not rejected — auto-list formatting seems desirable, and the prompt already permits it when requested.
A rejection is not a failure: it falls through to
deterministicCleanup, which reproduces the speaker's words verbatim. On the samples above, that path already produced correct output.validatechanged fromprivate statictostaticso tests can reach it, matchingcommandPromptand friends.Tests
New
Tests/SmartCleanupValidationTests.swift, registered in the runner and theMakefile, covering the logged regressions plus the cases that must keep passing: filler removal, self-corrections, punctuation fixes, auto bullets/numbered lists, preserved profanity, andallowsExpansiontransforms.make testpasses.Notes
This seems really fast. I think I might switch to this.→- I think I might switch to thisstill passes. Tightening it safely probably needs filler-aware content-word retention rather than a character ratio; happy to follow up if you want that.mustPreserveTermsis English-only and intentionally short. Easy to extend, though a broader list raises the cost of a false rejection (a needlessly basic-cleaned transcript).