Skip to content

feat: throw SmartApplyError when smartapply refuses a model rewrite - #10

Merged
mikkel merged 2 commits into
mainfrom
feat/smartapply-error
Oct 20, 2026
Merged

mikkel merged 2 commits into
mainfrom
feat/smartapply-error

Conversation

@mikkel

@mikkel mikkel commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Closes gap #3 from the JS↔Python parity audit. When Python's smartapply refuses a model rewrite it raises SmartApplyError. JS threw a plain Error, so callers couldn't tell an intent refusal apart from a provider or network failure.

This is a behavior change for refusals

When smartapply refuses a model rewrite, the object it throws changes:

before after
class / constructor Error SmartApplyError (err.constructor === SmartApplyError; still instanceof Error)
err.name 'Error' 'SmartApplyError'
printed form (String(err), first line of err.stack, uncaught output) Error: smartapply: refusing to write … SmartApplyError: smartapply: refusing to write …

Code that checks err.name === 'Error' or err.constructor === Error, or that snapshots the printed error, will see the difference.

What did not change:

  • The message text: smartapply: refusing to write "<path>": the model changed lines the diff did not add or remove. err.message checks and /did not add or remove/ matchers keep working.
  • When a refusal happens: the same condition, at the one call site.
  • Provider and LLM errors: they still propagate as the original error object. They're never wrapped, and they're never a SmartApplyError.

CHANGELOG.md (0.3.1, unreleased) now records this under "Behavior changes", and its summary no longer says smartapply is unchanged.

Changes

  • src/gptdiff.js: adds export class SmartApplyError extends Error, which sets name = 'SmartApplyError' and has the constructor (message), like Python's. It's thrown at the refusal site, and the smartapply JSDoc gains a @throws note.
  • src/index.js: exports SmartApplyError. package.json exports is unchanged.
  • .d.ts: regenerated with npm run types (tsc 5.9.3 from the lockfile). This also brings main's out-of-date src/filter.d.ts up to date (SNAKE_CASE_OPTIONS and the filterRelevant @throws note from filterRelevant: reject Python-style snake_case options #7). That part is generated type output only.
  • CHANGELOG.md: the 0.3.1 entry described above.
  • No landing/lib change, so no site redeploy is needed. landing/lib only holds copies of env.js, filter.js, llm.js and text.js.

Tests (test/smartapply_error.test.js, 6 tests, all LLM calls mocked)

  • The export: same class as the one in src/gptdiff.js, instanceof Error, name, and String(err).
  • A refused rewrite rejects with instanceof SmartApplyError and the exact previous message, and the input isn't mutated.
  • An empty rewrite is refused with SmartApplyError.
  • A provider error (status: 402) from the injected callLlmForApply comes through as the same object, not a SmartApplyError.
  • A TypeError('fetch failed') from callLlm passes through callLlmForApply unchanged.
  • The deterministic fast path still doesn't call the model.

Local run

npm test on Node 20.19.2 at the head: 238 tests: 234 pass, 0 fail, 4 skipped. The skips are the live tests, which only run with LIVE_LLM / GPTDIFF_LIVE, so no LLM calls were made.

Add SmartApplyError (extends Error, name 'SmartApplyError'), matching
patchling-py. smartapply throws it, with the same message as before, when the
model's rewrite changes lines the diff did not add or remove and the edit
cannot be replayed from the +/- lines. Provider and network errors are not
wrapped and still propagate as the original error object.

Export it from src/index.js. Regenerate the .d.ts files with npm run types;
this also brings src/filter.d.ts up to date for SNAKE_CASE_OPTIONS and the
filterRelevant @throws note, which were not regenerated in #7.

No landing/lib change: gptdiff.js and index.js are not vendored there.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Deploying patchling with  Cloudflare Pages  Cloudflare Pages

Latest commit: 0ceaeb6
Status: ✅  Deploy successful!
Preview URL: https://5c57599f.patchling.pages.dev
Branch Preview URL: https://feat-smartapply-error.patchling.pages.dev

View logs

The 0.3.1 summary said there was no change to smartapply. Refusals now
throw SmartApplyError: err.name, the constructor/class and the printed
form (String(err), stack first line) change. The message text and the
passthrough of provider/LLM errors do not.
@mikkel
mikkel marked this pull request as ready for review October 20, 2026 22:40
@mikkel
mikkel merged commit 69a4248 into main Oct 20, 2026
6 checks passed
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