Skip to content

Short origin follow-ups from the #72 review - #80

Merged
DennisAlund merged 20 commits into
mainfrom
fix/short-origin-followups
Oct 20, 2026
Merged

DennisAlund merged 20 commits into
mainfrom
fix/short-origin-followups

Conversation

@DennisAlund

Copy link
Copy Markdown
Member

Follow-up fixes from the review of #72. One commit per fix.

Fixes

  • The SDKs read links from older deployments. Python raised KeyError and Dart threw a TypeError on every call that returns a link when the Worker had not yet been redeployed. TypeScript returned shortOrigin as undefined. All three now fall back to the origin of the client's base URL. That origin drops a trailing slash, a path and any credentials.
  • Copy toasts report what happened. On plain http there is no clipboard, yet the API key modal said "API key copied" and the key, shown only once, was lost on Done. A failed copy now raises an error toast that names the URL to copy by hand. For the API key, the toast says the key is selected for a manual copy. Creating a link waits for the clipboard write before leaving the page, and announces "created & copied" only when the copy landed. The new strings are translated in en, id, sv and zh.
  • MCP link tools carry short_origin when the deployment pins one. This covers create, get, update, disable, enable, list, search, list-by-owner and bundle links. With nothing pinned the field stays off, because an MCP request often arrives on the mcp.* host.
  • get_link_qr lists base_url as required exactly when the deployment pins no SHORT_ORIGIN.
  • The Link constructor accepts a missing short origin again in Python and Dart, so a positional delta_pct works as before. In TypeScript Link is an interface, and the field stays required because every link the client returns carries it.
  • The origin is resolved in one place. fromLinkResult(c, result) resolves the origin from the request context instead of nine handlers repeating the lookup. The admin routes spread one layout object into <Layout>.
  • The browser suite covers the unset default. A second wrangler dev runs with SHORT_ORIGIN unset, and default-origin.spec.ts checks the copy button, the clipboard and the QR modal against it. Both e2e servers now boot fresh on every run, so a leftover server can no longer run the suite with the wrong environment.
  • The SDK README examples use the primary slug instead of slugs[0].

Notes for review

  • Test hardening, no weakening. Three MCP description tests built the agent with no env, which never happens at runtime, so they now get the test env. The existing copyUrl() unit tests now await the promise copyUrl() returns, because the toast waits for the write. Their assertions are unchanged.
  • No version bumps or CHANGELOG entries in this PR. Release commits go on main. The surface change from Pin short URLs to SHORT_ORIGIN #72 and these fixes call for minor bumps: npm 1.3.0, PyPI 1.3.0 and pub 2.3.0, with CHANGELOG entries covering both PRs. The spec did not change, so the hashes stay.

Verification

  • yarn test: 1670 passed. Typecheck clean.
  • yarn e2e: full suite green, including the new default-origin and no-clipboard steps.
  • scripts/test-sdks-e2e.sh: TypeScript, Python and Dart all pass against a live wrangler dev.
  • SDK unit suites: Python 120, TypeScript 103, Dart 116 passed. ruff, mypy and dart analyze are clean.

The REST link responses gained short_origin in #72, but the MCP link tools
did not, so an assistant on a deployment that pins SHORT_ORIGIN built URLs
from whatever host it knew. That is the wrong-domain case the setting
exists to prevent. The annotation now lives in withShortOrigin()
(src/short-origin.ts), and both surfaces use it: fromLinkResult() for REST
and a withOrigin() helper on the MCP agent for create, get, update,
disable, enable, list, search, list-by-owner and bundle links.

fromLinkResult() takes the request context instead of a resolved origin.
Nine handlers used to repeat resolveShortOrigin(c.env, c.req.url), and a
new link route that forgot it answered without a field the schema marks
required.

MCP leaves the field off when nothing is pinned. An MCP request often
arrives on the mcp.* host, which is not where short links live, so its
origin is no answer.

get_link_qr lists base_url as required exactly when the deployment pins
nothing. #72 made it optional everywhere, so a client that trusted the
schema on a default deployment left it out and the call failed.

Test hardening: three MCP description tests built the agent with no env,
a state the runtime never produces. init() now reads the environment to
shape the get_link_qr schema, so the fixture gets the test env. Their
assertions are unchanged.
Seven admin routes passed shortOrigin, theme, t and lang to <Layout> one
prop at a time, so #72 had to touch every route to add one shell prop, and
the next one would too. getPageData() now returns them as one layout
object that each route spreads. A new shell prop is added in one place.
#72 made copyText() resolve false instead of throwing when the clipboard is
missing or refuses the write. The callers ignored the answer and always
showed the success toast. On plain http, the deployment #72 adds support
for, the API key modal said "API key copied". The user clicked Done, and
the key, shown only once, was gone. quickShorten() and createDuplicate()
announced "created & copied" for a copy that never happened.

copyUrl() now returns the outcome. On success it confirms the copy. On a
failure it raises an error toast that names the URL to copy by hand.
copyRawKey() selects the key and says to copy it by hand. The two
create flows share copyThenOpen(), which waits for the write before
leaving the page, since the browser can abort a pending clipboard write
on unload. It confirms "created & copied" only when the copy landed.

The existing copyUrl() unit tests now await the promise copyUrl() returns
before reading the toasts, because the toast waits for the write to
settle. Their assertions are unchanged. The e2e comment that described a
synchronous toast is updated. New e2e steps drive both copy buttons with
navigator.clipboard removed.
#72 pinned SHORT_ORIGIN on the e2e server for the whole suite, so no
browser test covered the default most deployments run with. A regression
where data-short-origin renders empty, or where the client stops falling
back to location.origin, passed yarn e2e. A second wrangler dev now boots
with the variable unset, on the next port and its own state directory.
default-origin.spec.ts signs in there, creates a link, and checks that the
copy button, the clipboard and the QR modal all name that host.

Both servers now boot fresh on every run. A reused server carries the
environment it was started with: a server left running from before #72
failed the origin specs for a reason no test named. A port in use now
fails the run up front instead.
Link.from_dict() read data["short_origin"] strictly, so after a pip
upgrade every call that returns a link raised KeyError against a Worker
not yet redeployed: links.get/list/create/update/disable/enable,
slugs.lookup and bundles.links. The field now falls back to the origin
of the client's base_url, which is the origin such a deployment hands
out. Each resource passes that origin to from_dict().

Link also gained short_origin as a required field ahead of delta_pct.
That broke every Link(...) built by hand, including a positional
delta_pct. The field moves last with a default, so the constructor
accepts what it accepted before #72.

The README example builds the URL from the primary slug instead of
slugs[0]. After a custom slug becomes primary, slugs[0] can name a
different URL from the one the copy button and the QR code hand out.

No spec change, so the hash stays.
The transport hands back the camel-cased JSON as is, so against a
Worker not yet redeployed every link came back with shortOrigin
undefined, although the type promises a string. Each method that
returns a link now fills in the origin of the client's baseUrl when
the response has no short_origin. Those methods are links.get, list,
create, update, disable and enable, slugs.lookup and bundles.links.
That is the origin such a deployment hands out. The Python and Dart
SDKs make the same fallback.

The Python and Dart changes also make the Link constructor accept a
missing short origin again. TypeScript has no constructor: Link is an
interface, and the field stays required there because every Link the
client returns now carries it.

The README example builds the URL from the primary slug instead of
slugs[0], which can name a different URL from the copy button and the
QR code once a custom slug is primary.

No spec change, so the hash stays.
Link.fromJson() cast json['short_origin'] as String. After a pub
upgrade, every call that returns a link threw a TypeError against a
Worker not yet redeployed, because null is not a String. The field now
falls back to the origin of the client's base URL, the origin such a
deployment hands out. Each resource passes ShrtnrBaseClient.origin to
fromJson(). Uri.origin drops any credentials in the base URL.

Link's constructor also gained shortOrigin as a required named
parameter, which broke every Link(...) built by hand. The parameter is
optional now and defaults to empty, so the constructor accepts what it
accepted before #72.

The README example builds the URL from the primary slug instead of
slugs.first, which can name a different URL from the copy button and
the QR code once a custom slug is primary.

No spec change, so the hash stays.
@cloudflare-workers-and-pages

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

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
shrtnr c8439a3 Oct 20 2026, 12:09 AM

@DennisAlund DennisAlund left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Review of the short origin follow-ups: 15 inline findings, ranked roughly by severity. The first four can break a real call or a real run. The rest cover parity, UX, test accuracy and cleanup.

Comment thread sdk/python/README.md Outdated
# Build a short URL on the origin the deployment hands links out on
# Build a short URL on the origin the deployment hands links out on, from the
# primary slug: the one the copy button and the QR code use
config = client.config.get()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

config.get() fails on the deployments the new fallback paragraph promises to support.

/_/api/config arrived in #72 together with short_origin. On a Worker that has not been redeployed, client.config.get() raises ShrtnrError(404). Meanwhile link.short_origin works there through the new fallback. A reader who follows this example after a pip upgrade gets the 404.

The TypeScript (README.md:133) and Dart (README.md:135) examples have the same problem. Two ways out: build the example from link.short_origin, or give config.get() the same base-URL fallback on a 404 in all three SDKs.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 3cbd4b1. All three README examples now build the URL from the link's own short origin (link.short_origin / link.shortOrigin), which the SDK fills in on older deployments. The comment notes that config.get() needs a deployment that serves /_/api/config.

Comment thread src/client.ts Outdated
function copyThenOpen(link) {
var primary = pickPrimarySlug(link);
var copied = primary ? copyUrl(primary) : Promise.resolve(false);
return copied.then(function(ok) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Navigation now waits on the clipboard with no upper bound.

quickShorten() and createDuplicate() only navigate once writeText() settles. If the write never settles (for example an embedded webview, or a browser holding a permission prompt open), the link exists but the page shows no toast and never navigates. A second click creates another link.

Race the copy against a short timeout (about 1 s) and navigate on whichever finishes first. A timeout counts as ok === false.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in c0e09c0. copyThenOpen() races the write against a 1.5 s timer. A write that never settles counts as a failed copy, and the link page opens, which shows the short URL. New unit test: opens the link page when the write never settles (fake timers, for both quickShorten and createDuplicate).

Comment thread sdk/python/src/shrtnr/_base.py Outdated
def _origin_of(base_url: str) -> str:
"""The origin of the client's base URL: what a deployment older than the
``short_origin`` field hands short URLs out on."""
parts = urlsplit(base_url)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

urlsplit() can raise in the constructor, which breaks the documented error contract.

Shrtnr(base_url="http://[::1", api_key=...) now raises ValueError('Invalid IPv6 URL') from _SyncResource.__init__. The httpx.Client built a few lines earlier in Shrtnr.__init__ then never gets closed. Before this change, the first request raised ShrtnrError(0), which test_malformed_base_url_wraps_as_status_0 pins.

TypeScript (originOf) and Dart (on Object) both catch this and fall back to the URL as given. Wrap this call in try/except ValueError and return base_url.rstrip("/") to match them.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 5be5502. _origin_of() catches the ValueError from urlsplit() and .port and falls back to the base URL as given, so the constructor succeeds and the first call raises ShrtnrError(0), as in TS and Dart. Parametrized test covers http://[::1 and a non-numeric port.

Comment thread playwright.config.ts Outdated
url: `${UNPINNED_URL}/_/health`,
timeout: 120_000,
reuseExistingServer: false,
env: { E2E_PORT: String(UNPINNED_PORT), E2E_STATE: ".wrangler/e2e-state-unpinned" },

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The unpinned server can still be pinned by .dev.vars.

e2e-server.sh leaves SHORT_ORIGIN unset by omitting --var SHORT_ORIGIN. wrangler dev still loads .dev.vars from the project root. A developer who adds SHORT_ORIGIN there to try the feature then runs default-origin.spec.ts against a pinned server, and it fails with no hint about why. That is the stale-environment failure the reuseExistingServer: false change set out to remove.

Always pass --var "SHORT_ORIGIN:$SHORT_ORIGIN", even when empty. configuredShortOrigin() treats a blank value as unset. Check that --var takes precedence over .dev.vars in wrangler 4.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in c6448ca. scripts/e2e-server.sh always passes --var SHORT_ORIGIN:$SHORT_ORIGIN. I checked against wrangler 4.127.1 with a .dev.vars that sets SHORT_ORIGIN=https://oddb.it: without the flag the page rendered https://oddb.it, and with the empty flag it rendered the request origin.

Comment thread sdk/python/src/shrtnr/_base.py Outdated
# netloc keeps any user:password@ prefix; an origin never carries it, and
# a short URL built from it would hand the credentials out.
host = parts.netloc.rpartition("@")[2]
return f"{parts.scheme}://{host}" if parts.scheme and host else base_url.rstrip("/")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Python's fallback origin is not normalized the way the server, TypeScript and Dart normalize it.

URL.origin and Uri.origin lowercase the host and drop a default port. This keeps both as typed. With base_url="https://Shrtnr.Example:443", Python reports https://Shrtnr.Example:443 while TypeScript and Dart report https://shrtnr.example. That is one deployment and two different short URLs.

CLAUDE.md asks the SDKs to stay in step on base-URL handling. httpx.URL(base_url) already ships with the SDK and returns the scheme, host and port normalized.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 5be5502. The fallback now lowercases scheme and host, drops a default port and keeps IPv6 brackets, matching URL.origin / Uri.origin. Parametrized test: https://Shrtnr.TEST:443 gives https://shrtnr.test, HTTP://shrtnr.test:80/api gives http://shrtnr.test, and a non-default port and [::1] are kept.

Comment thread src/client.ts Outdated
// link page that opens next shows the short URL.
function copyThenOpen(link) {
var primary = pickPrimarySlug(link);
var copied = primary ? copyUrl(primary) : Promise.resolve(false);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

copyThenOpen() gets toasts from copyUrl() that it throws away right after.

On success, Copied {url} is replaced in the same tick by created & copied. On failure, the red "copy it by hand" toast flashes and the navigation discards it.

Call copyText(shortOrigin() + '/' + primary) directly and keep the one toast this function decides on.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in c0e09c0. copyThenOpen() calls copyText() directly, so only 'created & copied' shows on success, and no toast flashes before the navigation on failure.

"""Get a link by ID. Optional range scopes the click-count window."""
url = self._url(f"/_/api/links/{id}", {"range": range})
return Link.from_dict(self._request("GET", url, headers=self._headers()))
return Link.from_dict(self._request("GET", url, headers=self._headers()), self._origin)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The fallback is repeated at every call site, so the next method that returns a link can miss it.

Python has 16 hand-written self._origin arguments. TypeScript and Dart have 8 withShortOrigin and defaultOrigin: call sites each. A future method that returns a Link and skips this brings back the KeyError, TypeError or undefined this PR fixes.

The server commit moved the lookup into fromLinkResult(c, ...) for exactly this reason. The SDKs could do the same: a _link(data) and _links(data) helper on the resource base classes, a requestLink and requestLinks pair on HttpClient, and a helper on ShrtnrBaseClient.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 174b671 (TS HttpClient.requestLink()), 8fa4129 (Python _request_link() / _request_links()) and c8439a3 (Dart requestLink() / requestLinks()). The origin is private to the transport in TS and Dart, so a resource cannot pass it by hand. The Python fallback test now covers create, update, disable and enable too, as the TS and Dart suites already did.

Comment thread src/client.ts
// Both become false here rather than a throw, and every caller reads the
// answer: a toast that announces a copy that never happened is worse than
// none, since the user moves on without the text.
function copyText(text) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This reports the plain-http failure accurately, but it does not fix it.

On http://192.168.x.x, navigator.clipboard is undefined, so every chip click and every API key copy now fails, with an error toast each time. A hidden <textarea> plus document.execCommand('copy') still works in insecure contexts when it runs inside the click gesture, and both copyRawKey() and the chip handler run inside one.

Try navigator.clipboard first, then the execCommand fallback. Keep the error toasts for the case where both fail.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not done in this PR. e2e/short-origin.spec.ts › "without a clipboard" pins the current rule: no navigator.clipboard means an error toast naming the URL. With an execCommand fallback, Chromium on localhost would copy successfully and that spec would fail. Changing it is a behavior decision for the maintainer, not a review fix, so I'm leaving it open.

id: id ?? this.id,
name: name ?? this.name,
description: identical(description, _unset) ? this.description : description as String?,
description: identical(description, _unset)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Unrelated dart format changes ride along in the fix commit.

e923741 also reformats Bundle.copyWith, BundleWithSummary, ClickStats, TimelineSummary and the shrtnr.dart export list. CLAUDE.md says: "One coherent change per commit (one refactor, one feature, one fix) ... Do not batch unrelated changes." The formatting buries the two-line behavior change. A separate formatting commit would keep the fix easy to review.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Won't fix. e923741 is already pushed, and splitting it means rewriting published history with a force push, which this repo never does. Every commit after it keeps formatting out of the behavior change.

Comment thread playwright.config.ts Outdated
},
{
// SHORT_ORIGIN unset, the default most deployments run with.
command: "bash scripts/e2e-server.sh",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The second server boots on every Playwright run.

That includes yarn perf (playwright test e2e/perf.spec.ts) and single-spec runs that never reach default-origin.spec.ts. Each one now pays for a second migration and wrangler dev boot, and needs port 8798 free or it fails up front.

You could start this server only when the default-origin project runs, for example behind an env flag that yarn perf leaves unset.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 18f3290. yarn perf sets E2E_PINNED_ONLY=1, which drops the second server and the default-origin project. yarn e2e still boots both.

The no-clipboard and rejected-write cases asserted 'Copied ...'. They passed
only because the stub t() ignored its key and the stub toast() dropped the
type, so a copyUrl() that announced a failed copy as a success stayed green.
The stub now echoes the key and records the type, and both cases assert the
copyFailedUrl error toast the code emits.
On the links list the 'Copy it by hand: {url}' toast is the only place the
full pinned-origin URL appears, and it hid after 3 s like every other toast.
It now stays for 10 s. toast() takes the duration and clears the previous
toast's hide timer, which used to hide a newer message early.
copyThenOpen() waited on navigator.clipboard.writeText() with no limit. A
write the browser leaves pending (an unanswered permission prompt, a webview
that never settles it) left the user on the page with no toast and no
navigation, and a second click created a second link. The wait now ends
after 1.5 s and the link page opens, as it does for a failed copy.

It also calls copyText() directly. copyUrl()'s own toast was replaced in the
same tick by 'created & copied' on success and discarded by the navigation
on failure.
The second e2e server left out --var SHORT_ORIGIN to run with the variable
unset. wrangler dev still loads .dev.vars, so a developer with SHORT_ORIGIN
set there got a pinned server on :8798, and default-origin.spec.ts failed
with no hint why. The script now always passes the flag; an empty value
overrides .dev.vars (checked against wrangler 4.127.1 with a .dev.vars that
sets SHORT_ORIGIN: the page renders the request origin).
Its header claimed to catch a script that ignores location.origin. The server
always renders the request origin into data-short-origin when SHORT_ORIGIN is
unset, so the script's fallback never runs in this spec: a shortOrigin()
returning pinned || '' still passes. The header now names what the spec
guards (the server's fallback) and points at the unit test that covers the
script's.
Every Playwright run booted the second wrangler server, including yarn perf,
which runs one spec against the pinned server. It paid for a second
migration and boot, and failed up front when :8798 was taken. yarn perf now
sets E2E_PINNED_ONLY, which drops that server and the default-origin
project.
…owups

# Conflicts:
#	sdk/dart/README.md
#	sdk/python/README.md
#	sdk/typescript/README.md
The example in all three SDK READMEs called client.config.get(), which
answers 404 on a Worker deployed before #72: the deployments the fallback
paragraph says the SDK supports. It now reads the link's own short origin,
which the SDK fills in there.

It also picked the primary slug, else the first slug. The copy button and
the QR code (pickPrimarySlug) try the first custom slug before the generated
one, so a link with no flagged primary printed a different URL than the UI
handed out.
Two departures from the server and the other SDKs in _origin_of():

- It kept the host's case and a default port. https://Shrtnr.Example:443
  gave https://Shrtnr.Example:443, where the server, TypeScript (URL.origin)
  and Dart (Uri.origin) give https://shrtnr.example. Scheme and host are now
  lowercased and a default port dropped.
- urlsplit() raises ValueError for an unclosed IPv6 bracket, so
  Shrtnr(base_url='http://[::1') raised from the constructor and leaked the
  httpx.Client built just before. TypeScript and Dart construct the client
  and raise their documented error on the first call. An unparseable base
  URL now falls back to itself and fails there with ShrtnrError(0).
Link.fromJson replaced only a null short_origin (??). Python (or) and
TypeScript (truthy check) also replace an empty string, so a response
carrying short_origin "" gave the base origin there and '' in Dart, which
then built '/abc'. Dart now falls back on an empty value too.

The same case is pinned in the TypeScript and Python suites, where it
already held.
Each of the eight Link-returning methods appended
.then((value) => withShortOrigin(value, this.http.origin)) by hand. The next
method that returns a Link and forgets it brings back the undefined
shortOrigin this branch fixes. HttpClient.requestLink() now applies the
fallback, and the origin is private to it, so a resource can no longer pass
it around itself.
Sixteen call sites passed self._origin to Link.from_dict() by hand. The next
method that returns a Link and forgets it brings back the KeyError this
branch fixes. _request_link() and _request_links() on the shared resource
base now apply the fallback, as HttpClient.requestLink() does in TypeScript.

The fallback test now covers create, update, disable and enable as well,
matching the TypeScript and Dart suites, which already swept every
Link-returning method.
Eight call sites passed defaultOrigin: _http.origin to Link.fromJson() by
hand. The next method that returns a Link and forgets it brings back the
TypeError this branch fixes. ShrtnrBaseClient.requestLink() and
requestLinks() now apply the fallback, as the TypeScript and Python SDKs do,
and the origin is private to the base client.
@DennisAlund
DennisAlund merged commit 335b5cd into main Oct 20, 2026
10 checks passed
@DennisAlund
DennisAlund deleted the fix/short-origin-followups branch October 20, 2026 00:10
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