Skip to content

feat!: 3.0.0 reads the API's 2026-10-11 shape; 2.x names kept, deprecated - #108

Draft
lenzhq wants to merge 56 commits into
mainfrom
feat/api-version-2026-10-11
Draft

lenzhq wants to merge 56 commits into
mainfrom
feat/api-version-2026-10-11

Conversation

@lenzhq

@lenzhq lenzhq commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

What changes

3.0.0 reads the API's 2026-10-11 response shape. Every request sends X-Lenz-API-Version: 2026-10-11 (also with a caller-supplied HTTP client), and the SDK reads only that shape for its own calls.

  • Webhooks still parse both shapes. A receiver gets events in the version of the request that started the work, which may come from an older client.
  • 2.x code keeps working. Every 2.x name is kept with its 2.x value, computed from the new shape, and marked deprecated with its replacement (table in the CHANGELOG). Error classes and their fields keep their 2.x values. The new names are the primary documented interface.
  • New LenzApiVersionError: a response whose X-Lenz-API-Version header names another version (in practice 2026-05-13) is refused instead of misread. It is never retried, never swallowed by a 404 shortcut or a poll, and never applied to webhooks.
  • Breaking (all in the CHANGELOG):
    • raw bodies show the wire shape;
    • recorded 2.x response fixtures must be re-recorded;
    • webhook receivers older than 2.21 must upgrade before their senders move to 3.0;
    • values the API no longer sends: chain_id, the review/citation-check webhook task_id, some reworded failure sentences and hints.

Developer-experience pass (in this PR)

Reviewed with fresh-eye DX reviews, an engineering plan review, Codex and adversarial reviews.

Fixes:

  • Batch submit and ask.send send an automatic Idempotency-Key, like the other paid calls. A retry reuses it. An in-flight 409 idempotency_conflict is retried with the same key, and idempotency=False opts out.
  • Every error from a keyed call carries idempotency_key. A resend is safe only with that key.
  • New error classes: LenzNotFoundError (404), LenzConnectionError and LenzRequestTimeoutError. They are subclasses of what was raised before, so existing except/catch clauses still match.
  • retryable on every error: the server's value when it sends one, else derived from the status.
  • Waits stop on 401/403/404 and version errors instead of polling to a misleading timeout. In a batch, a 404 or version error fails only that item.
  • Poll requests never pass the wait's deadline.
  • Typed values: verdict, confidence and depth are typed for autocomplete, and any string is still accepted.
  • Webhooks: a typed .verification on verification events. Node gets isEvent() narrowing and eventId on every event.
  • Pagination: iterators over verifications and library (iter() / listAll()).
  • Docs: a "first call" at the top of the README, an "Upgrading from 2.x" box in the CHANGELOG, and runnable, type-checked review and citecheck examples.

The CHANGELOG lists every intentional behaviour change under "Changed". Everything else behaves as on 2.21. Request bodies are byte-identical, checked across 100+ call variants.

Cancelled tasks (in this PR)

In API version 2026-10-11 a task can end as cancelled (stopped from the website or another client). 3.0 treats it as final everywhere:

  • wait, verify_and_wait, verify_batch_and_wait, review_and_wait, citecheck_and_wait and the CLI end at once with the same error class and failure fields as 2.x (failure_class cancelled, retryable False). A batch item is failed with the cancelled status in status_detail.
  • Getters return the object with status == "cancelled"; TaskStatus keeps the 2.x flat fields filled.
  • VerificationCancelled and the review.cancelled / citecheck.cancelled webhook events are typed. Older-shape cancellations still arrive as *.failed.
  • CHANGELOG lists this under Breaking: hand-written polling loops must treat cancelled as final, and webhook receivers must handle *.cancelled. A 2.21 receiver reads them as a plain WebhookEvent.

Stopping a run (in this PR)

  • client.cancel(task_id) -> CancelResult, client.cancel_review(review_id) -> ReviewFull and client.cancel_citecheck(citecheck_id) -> Citecheck call the API's cancel endpoints. They send no body and no Idempotency-Key; repeating one is safe.
  • cancelled is True whenever the run is cancelled, by this call or an earlier one. False comes with the run's status, normally completed or failed.
  • A 404 keeps the server's code (not_found). A review's deep check answers 409 use_review_cancel: it is sent once, never retried, and its fix names cancel_review.
  • Every id in a request path is now sent as one percent-encoded segment, and an empty id, . or .. raises ValueError before any request (CHANGELOG, Changed).
  • The live smoke test cancels a low-depth verify and checks both outcomes, so the release gate exercises the endpoint.
  • openapi.json refreshed (version 2026-10-11 and the cancel endpoints), byte-identical with the Node SDK's copy.

Tests

ruff, strict mypy and the full suite pass (93.5% coverage). Parity fixtures are synced with the API's recorded responses. Every 2.x attribute is compared against 2.21.0's value. The guard is tested on success and error responses, stored replays, a custom client, polling, delete and webhooks. Deprecated fields carry PEP 702 or JSON-schema markers.

Release

Publish only once lenz.io serves 2026-10-11. Before that, the release workflow's live smoke run fails and the tag does not publish.

Post-release actions and prod checks

  1. Mark ready, date the CHANGELOG section, merge, tag v3.0.0. The release runs the live smoke first.
  2. Check the package page shows 3.0.0, then run one extract, assess and verify against lenz.io with a real key.
  3. Bump the demo apps to 3.0.

🤖 Generated with Claude Code

lenzhq and others added 10 commits October 8, 2026 20:07
…on verify

Every model, the webhook parser, the error mapping and the CLI read the
API's newer response shape as well as the original one. The newer field
names are attributes (ExtractedClaims.claims, AssessClaim.status/failure/
more_claims, TaskStatus.failure, completed_at, claim, FailureBlock.code,
the *_limit_exceeded flags, webhook event_id), and every older attribute
keeps its original meaning, computed from whichever shape arrives.
model_dump() returns the shape the server sent.

verify and verify_batch leave webhook_url out instead of sending "" (both
meant the key's default); review and citecheck bodies are unchanged.

The /me/usage deprecations no longer name a removal date.

Parity tests freeze what the previous release produced from each
original-shape response (models, CLI text and JSON, wait outcomes, errors,
webhooks, request bodies) and hold both shapes to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d on newer-shape review webhooks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…arlier release

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… properties

A body is read as the newer response shape only when it carries a key only
that shape has; every other body goes through the same fields, values,
dump, repr, exclude_unset, JSON schema and pickle as before. The newer
names (claims, status, failure, more_claims, completed_at, claim, code,
detail, the *_exceeded flags, webhook event_id / failure / claims / reason)
are read-only properties, not fields, and the model-level serializer is
gone.

Errors read the newer failure block, retry_after and a computed remaining
only for a newer-shape body and only where the original key is absent.
Webhook events keep their dataclass fields; a needs_input option with a null
domain no longer raises. The CLI renderers are back to 2.20's code.

Parity fixtures are rebuilt from the real recorded pairs (responses and
webhooks), extract goes through the client with the recorded locate, and the
oracle now also freezes exclude_unset dumps, repr, JSON schemas and pickles.
The remaining removal-date text is gone, and the changelog lists what to
settle before the SDK asks for the newer shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ew_id / citecheck_id as task_id

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ributes keep their values

Every request now sends X-Lenz-API-Version: 2026-10-11. Every attribute the
SDK had in 2.x reads the value it read then, computed from the current
shape where the server renames or leaves a field out:

- /assess: the no-claim error sentence, the compound-claim hint on a verdict
  row; review deep checks, issues and verification-stage failures spell
  "nothing checkable" not_a_claim.
- /me/usage blocks projected with the original prices when none is
  published, quota_used never negative.
- verification.completed webhook result: every original key and default.
- Errors: a 422 keeps its original code, message and errors per endpoint
  (map_response_to_error takes the request's endpoint); a citation-check
  402 reports credit_balance; TaskAccepted.chain_id reads "" when absent.

The parity suite now holds every recorded response pair to the previous
release's output with narrower allowances (only values the current shape
cannot carry), and the citation 402 fixture follows the current server.

BREAKING CHANGE: raw access (model_dump(), exc.body, event.raw, the CLI's
--json) shows the current response shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… no field errors, as in 2.x

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ers webhook versions and every value that reads differently

On verify and verify_batch a blank or whitespace-only webhook_url (and a
None) meant the key's default webhook in 2.x; the current API version reads
a blank value as "no webhook", so the SDK leaves it out.

The 3.0.0 changelog now says that work submitted with 3.0 sends its webhooks
in the current shape (receivers must run 2.21+ first), and lists the 409
hint, the 400 codes, older stored review rows and the review/citecheck
webhook task_id among the values that read differently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s 2.x sentence

- Errors: the whole original error body is rebuilt per endpoint, as the Node
  SDK does: `code` is "" where the 2.x error carried none (the API now sends
  one on every error), plus the 422 envelopes, original wait and link names,
  and a citation-check 402's pool balance. `exc.body` is the body as sent.
- A failed poll's `error` (and the LenzPipelineError message) reads its 2.x
  sentence, rebuilt from the code: the fixed sentence for cancelled,
  task_stuck, task_error and not_a_claim, else "Pipeline stopped at: <code>".
- The parity suite no longer allows a new error code; the changelog's list of
  what reads differently matches the Node SDK's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rror names its item

- `internal_error` and `invalid_request` read as "" on the exception, as in
  2.x, where the error carried no code.
- A verify_batch item with an unsupported language: the message is
  "claims[<n>].<sentence>" again, rebuilt from the error's location.
- Changelog: the headline names the one exception (raw bodies show the newer
  shape), and the failed-poll entry says task_error / task_stuck read one
  fixed 2.x sentence where 2.x had several.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from feat/read-both-api-shapes to main October 8, 2026 21:32
lenzhq and others added 12 commits October 9, 2026 00:34
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3.0 reads only the 2026-10-11 response shape for its own calls (webhooks of
both shapes are still parsed). The current names are the primary interface in
the README, examples, CLI and docstrings; every 2.x alias stays with its 2.x
value, documented as deprecated, listed in the changelog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The current shape leaves the pool out of a citation check's 402 because it
equals remaining; the endpoint-aware conversion already restores it for that
call. Drop the generic fallback that invented a pool figure on every other 402.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…version raises LenzApiVersionError

The header is now set per request, so an http_client= with no header or a stale
default still asks for 2026-10-11. A response (success or error) whose
X-Lenz-API-Version names another version raises LenzApiVersionError, carrying
status_code, the body as sent and api_version, instead of being misread. A
missing header proceeds; webhook parsing is not guarded; batch waits re-raise it
rather than treating it as a failed poll.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aim, as 2.x did

TaskStatus.failure and the failure block of a verification.failed event read
failure_reason 'not_a_claim' from a current-shape body. Assessment and review
failure blocks keep 'no_claim'; failure.code is no_checkable_claim throughout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A completed review assessment with other claims found reads the 2.x sentence in
hint; a failed one reads None, as before. The parity exemption that hid the
difference is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 2.x names kept as fields (claim, identified_claims, locations, error_code,
hint, error, failure_*, claim_text, modified_at, *_limit_reached, the Usage
capacity blocks and quota_resets_at, and the rest) carry deprecated in their
schema, as candidate_claims already did. The schema-compatibility test ignores
the marker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rename the stored-replay, stored-progress and live-token cases to the names the
server records them under, add the cases that were missing in both shapes (a
needs-input 409 and the poll it names, a purged 410, an idempotent ask replay,
a retried webhook delivery) with the 2.21 oracle for each, and drop the
current-shape twins of the stored replays: the API answers a replay stored by an
older release in the old shape and says so in the version header, which the
version-guard tests now cover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e version guard

List the values the API no longer sends as breaking, say truthfully what
model_dump() and --json return (the 2.x-compatible fields plus the keys the
server sent; exc.body and event.raw are the body as sent), document
LenzApiVersionError, and name the replacements for AssessClaim.hint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The API version a response is served in is not a setting a caller can
change; the error now says to contact support if it persists, and that
2.x reads both versions. Same wording in both SDKs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An idempotent delete, the CLI's 404 fallbacks, the verification poll and
an unreadable body could each turn LenzApiVersionError into success, a
retry or another error. Each now lets it through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lenzhq lenzhq changed the title feat!: 3.0.0 asks for the API's current response shape (2026-10-11) feat!: 3.0.0 reads the API's 2026-10-11 shape; 2.x names kept, deprecated Oct 9, 2026
lenzhq and others added 6 commits October 9, 2026 13:56
…ignature changes

Each call is sent with its options left out, empty / zero / false and set,
under a pinned Idempotency-Key, through the public methods, and the body is
compared key order included: a changed body turns an idempotent replay into
a 422.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One random key per call, reused across that call's own retries, so a
retried batch or follow-up question replays the first answer instead of
being charged twice. A caller's key wins; idempotency=False sends none. An
in-flight 409 is raised as before, never passed with a second key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and retryable on every error

A 404 raises LenzNotFoundError (a LenzError) whose fix says what to check
instead of advising a retry. A network failure raises LenzConnectionError
and a transport timeout LenzRequestTimeoutError, both subclasses of the
LenzAPIError 2.x raised, with the httpx exception as __cause__. Every error
carries retryable, set at construction: the server's value on a failed job,
else derived from the status and class; a boolean in the body's failure
block wins. ReviewFailedError, ReviewTimeoutError, CitecheckFailedError and
CitecheckTimeoutError alias the job errors under the Node names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… timeout

wait, verify_and_wait and verify_batch_and_wait polled through every error
until their deadline, so a revoked key or a wrong task id surfaced minutes
later as a LenzTimeoutError. They now end with the error (a batch item is
failed, the others continue); a 5xx, a 429 or a network failure is still
polled again, after any wait it stated. Each poll is one request bounded by
what is left of the deadline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For comparisons, exhaustive matching and docs, named like the Node SDK's.
The model fields and method arguments stay str, so a value a later API adds
still reads and still sends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nvelope

VerificationCompleted, VerificationFailed and VerificationNeedsInput expose
.verification: the verification as client.get_status returns it (a typed
TaskStatus, the verdict under .verification.result), built from either
payload shape, None when the payload cannot be read as one. A property, so
the events' fields and repr are unchanged; the dict result stays. A
malformed nested result no longer crashes parse_webhook. The FastAPI example
reads it and handles review and citation-check events.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lenzhq and others added 28 commits October 9, 2026 14:09
Lazily, one page when its first item is asked for, from the start page, with
the page size read from each response, stopping after a short or empty page.
library.iter takes list's filters and refuses sort='random', which is not
exhaustive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The options they forwarded through **kwargs are keyword-only parameters with
the same defaults, so editors complete them and type checkers check them.
Positional claim/text and every default are unchanged, the request bodies
match the frozen baseline, and an unknown option is still a TypeError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, the 3.0 changes

The README opens with a ten-line first call (key, assess, verdict and
confidence); the CLI moves below the review and citation-check sections. The
quickstarts no longer fail when no claim was escalated, read failed rows by
status == "failed", and give depth="low" its real price; one count for the
API's calls. New examples/core/review_draft.py and citecheck_draft.py, and
CI type-checks every example (which caught quickstart.py reading a reply
field that does not exist). The README and CHANGELOG cover the new errors,
retryable, waits, automatic keys, iterators, webhook .verification and the
Literal aliases, with an upgrade box and the replay note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…or refusal

The 404 fix reads "Check the id or key the call names: nothing with it is
visible to this credential. Retrying will not help." retryable is None when
there was no HTTP status (a missing key, a wait timeout, a needs-input pause,
a webhook signature), and a top-level boolean retryable wins after the
failure block's. library.iter's sort="random" refusal uses the Node words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…not_ready, unknown without a status

A 409 that says "not yet" is worth resending (with the same key); the
body's code is read as sent, so the 2.x attributes keep their empty code.
A LenzAPIError or LenzRateLimitError with no status reads None, as in the
Node SDK; the connection classes stay True. Declares
LenzError.idempotency_key (None by default), wired in the next commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… every error carries the key

A call that sent an Idempotency-Key and meets the first request still
running sends the same key and body again, after the stated wait (capped)
or the usual backoff, inside its retry budget; still conflicting, it
raises the 2.x error with retryable=True. A review or citation-check
conflict that names the job still returns it at once. Every LenzError a
keyed call raises (its wait included) carries exc.idempotency_key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ns past the deadline

One helper derives each poll's timeout from the client in use (its read
timeout; None means no client cap; an injected http_client keeps its own),
fixing the TypeError a non-float Lenz(timeout=) raised in every wait. A
poll is bounded by what is left of the deadline and none starts once it is
spent (the first poll of a wait still always runs, so timeout=0 reads
once as in 2.x); the ids left are timed out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and raises a 401 or 403

A LenzApiVersionError, 404 or 410 on one item's poll is that item's
outcome (failed, no status_detail); the others keep being polled. A 401 /
403 refuses the key itself, so the batch wait raises it. A single wait
still raises every one of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…out a page size, and refuses a start page below 1

verifications.iter() and library.iter() also stop once the pages read
reach the response's total, when the response states no usable page_size,
and (without yielding it) when the server answers another page than the
one asked for. A start page below 1 raises ValueError at call time, as
sort="random" does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, retried and polled through

httpx.TransportError (a server that hung up, a proxy failure, a read or
write error) is retried like a network error and raised as
LenzConnectionError; a wait polls again after one instead of aborting.
UnsupportedProtocol and LocalProtocolError (a request that could never be
sent) stay the httpx exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ults on a sparse payload

A key the payload left out reads as event.result (and the Node SDK) read
it: visibility "private", depth "standard", created_at "" and the rest,
in both payload shapes, never the Verification model's own defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A caller's list[dict[str, str]] now type-checks (a list is invariant;
Sequence is not); nothing changes at run time. The examples drop the
annotation they needed for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tatus is another kind

A verification.completed carrying a failed run (or any status that is not
the event's) is not presented as a completed verification, as in the
Node SDK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the class 2.x raised

A keyed call whose response cannot be decoded still raises the
JSONDecodeError 2.x raised, now with exc.idempotency_key. Pins that the
*_and_wait helpers keep going through the methods 2.x called (review,
citecheck, wait), so an override is honoured and gets the call's key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e and the batch outcomes

README errors, retry and idempotency sections, the ask.send docstring and
the CHANGELOG say a resend is safe only with exc.idempotency_key, that a
409 idempotency_conflict is sent again inside the call, how a wait treats
its deadline and a batch item's 404 / version error / 401, and the
iterator and transport-error changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y configured timeout phase

Only a timeout <= 0 wait reads each status once regardless of the
deadline (as in 2.x); otherwise an item reached after an earlier poll
spent the deadline is timed out, not polled (with timeout=None it could
hang). Each poll keeps the client's connect, read, write and pool
timeouts, each capped by what is left of the deadline, instead of one
scalar for every phase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ncy key

Response validation of every keyed call (verify, verify_batch, extract,
assess, select, ask.send, review, citecheck) runs inside the
key-carrying context: a pydantic ValidationError keeps its class and
gains exc.idempotency_key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n unreadable answer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d *.cancelled webhooks are typed

In API version 2026-10-11 a verification, review or citation check stopped
elsewhere (the website's Stop button, another process) is the status
`cancelled`. The wait helpers stopped only on completed, needs_input and
failed, so they polled such a task until their timeout. They now end on it
with the error 2.x raised for the original shape (failure_class `cancelled`,
retryable False); a batch item is a failed row; the plain getters return the
cancelled status without raising. `verification.cancelled`
(VerificationCancelled), `review.cancelled` and `citecheck.cancelled` parse
as typed events; a cancellation of older work keeps arriving as *.failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD
…cancelled documented as a breaking change

The verification envelope of an event now builds a cancelled status only for
verification.cancelled and a pause only for verification.needs_input; any
other event kind has none. A cancelled status with a null failure still reads
the 2.x fields. The CLI words a cancelled batch row's details as Cancelled.
The parity test anchors the failure-block exemption to the top-level blocks
and compares the CLI text with the cancelled line normalised out. The example
receiver handles the cancelled events. The changelog lists the cancelled
status and events under Breaking and amends the receiver upgrade step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD
client.cancel(task_id) returns a CancelResult (task_id, cancelled, status);
cancel_review and cancel_citecheck return the same models get_review and
get_citecheck do. All three answer 200 whatever the state of the run, send no
body and no Idempotency-Key, and are retried on a 5xx or a dropped connection.
A 409 use_review_cancel (a review's deep check) raises a LenzError whose fix
names cancel_review and is sent once, never waited on or resent. The smoke
test cancels a depth=low run right after it starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD
…le after_a_while copy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD
…refuses a wrong answer

Every id that goes into a URL path is percent-encoded whole by one helper, so
../, ?, # and / in an id cannot leave the intended path; an empty id, . and ..
raise ValueError before any request. The cancel calls keep the server's error
code (not_found, use_review_cancel) and raise LenzAPIError for a 200 that is
not the task, review or check asked for. The smoke test cancels twice and waits
30s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD
…repeat, note path-encoded ids

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD

This branch has not been deployed

No deployments
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