Every error a @fkn/lib 0.9.49 call can produce has a row on this page, and a few rows list more than one wording of the same error. This page groups the rows by the calls that produce them and says, for each, what happened, what to do and whether a retry can succeed.
handling errors explains how to match a row. It also lists which fields of an error survive the hop out of the broker, the connection your app holds into FKN.
The handler below sorts an error into five branches by the shape it arrives in. The guards overlap, so test them in this order:
Returns true if the sequence of elements of searchString converted to a String is the
same as the corresponding elements of this object (converted to a String) starting at
position. Otherwise returns false.
startsWith(
constE2E_STALE_EPOCH_MESSAGE:"fkn:e2e-stale-epoch: this file is encrypted under a previous key you reset"
Returns true if the sequence of elements of searchString converted to a String is the
same as the corresponding elements of this object (converted to a String) starting at
position. Otherwise returns false.
startsWith(
constE2E_INTEGRITY_MESSAGE:"fkn:e2e-integrity: stored data failed its integrity check"
Returns true if the sequence of elements of searchString converted to a String is the
same as the corresponding elements of this object (converted to a String) starting at
position. Otherwise returns false.
Returns true if the sequence of elements of searchString converted to a String is the
same as the corresponding elements of this object (converted to a String) starting at
position. Otherwise returns false.
startsWith('FKN: the broker was replaced')) return
constretryOnce: () =>Promise<string|Buffer>
retryOnce()
// locators and frames, by name, through the exported guards
handle) // the catalog, or null when nothing is stored yet
On a first run nothing is stored yet, so the read resolves to null. The closing throw passes on a PackagesError or a node-style storage code. Both are matched on code, as their sections below show.
The same handler serves every call site. A storage read never reaches the locator, consent or broker-deadline branches. A realm is one JavaScript execution context, such as a window, a worker or a package tenant. handling errors lists which errors cross out of one.
Type any part of a message, a name, a call or an instruction to narrow every table at once:
Each row has an anchor made from the fixed prefix of its message, the part you match on. /errors/#storage-api-unreachable keeps working for as long as that prefix does. When two rows share a prefix, the later row carries an anchor of its own, as Permission denied: <category> on <site> does.
The target hostname is a name FKN owns or any dot-anchored subdomain of one, or the address of an FKN node in any spelling. Through @fkn/lib 0.9.46 the page checked only fkn.app, fkn.dev and sdbx.app, and the broker refused the rest; from 0.9.47 the page checks the full list. A trailing dot is stripped first, so fkn.app. and fkn.app%2e are the same name.
Point the call at the app's own api. A capability that carries the user's identity is never aimed at a platform origin.
No
fetch: refusing to target the extension's own pages or FKN platform domains
Error
extension.fetch, and the root fetch whenever the extension backend is chosen
The url scheme is chrome-extension: or moz-extension:, or the hostname is a platform host. The extension checks this a second time in its content script, so calling the bridge directly does not skip it. From the first extension store release after 0.1.54 it checks every name FKN owns, and checks once more in its own worker.
Point the call at the app's own api, as for the row above.
No
<call>: refusing to target FKN platform domains
Error, or LocatorDeniedError on a locator call
cookies.get, setRequestHeaderRule, the category form of permissions.request, extension.attachFrame and attachFrame on the extension with domains, and extension frame.goto, since @fkn/lib 0.9.46
A host the call names is a name FKN owns or a subdomain of one, or an FKN node's address; through @fkn/lib 0.9.46 your realm checked only fkn.app, fkn.dev and sdbx.app. For a domain list a header rule is built from (setRequestHeaderRule, the domains of an attach or a goto, and a goto target's host), a parent of one counts too, such as app or .dev, since a rule matches every host under each domain. <call> is the entry, for example cookies.get or frame.goto. Thrown in your realm before anything is asked or armed. From the first store release after 0.1.54 the extension refuses the same again, on the same full list, and also refuses a locator call, frame.fetch included, whose document or target is on one, with the operation as <call>.
Drop the FKN host, or the parent domain that reaches one, from the call. No call is ever aimed at a platform host.
No
fetch: refusing a redirect onto FKN platform domains
Error
extension.fetch, and the root fetch on the extension path, from the next FKN extension store release on
The fetch followed a redirect whose final url is on an FKN platform host. A set of browser rules blocks such a hop before it is sent, on Chromium and Firefox and in every spelling of the host, so the fetch normally rejects as on a network error instead. This error is the answer should a hop ever pass those rules, and the response body is discarded unread. Raised from the first store release after 0.1.54.
Point the request at a url whose redirects stay off FKN hosts.
No
fetch: could not install the rule that keeps a redirect off FKN platform domains, so the request was not sent
Error
extension.fetch, and the root fetch on the extension path, with redirect other than 'manual' or 'error', from the next FKN extension store release on
The extension installs a set of session rules each time its worker starts, which block its own requests to FKN platform hosts so a redirect hop onto one is never sent. The browser refused to install them, so a fetch that follows redirects is refused before it is sent rather than sent without the rules. Raised from the first store release after 0.1.54.
Pass redirect: 'manual' or redirect: 'error' when the call need not follow a redirect; those are sent as before. Otherwise retry later.
Yes, once the extension's worker restarts and installs the rule
fetch with credentials needs the FKN extension, which only exists in window realms
Error
root fetch with credentials: 'include'
The realm has no document, so there is no content script to reach and no session to spend.
Do the credentialed call on the main thread, or drop to credentials: 'omit' and let the cloud backend serve it.
No
The FKN WebExtension is not installed, enabled or not exposed on this page.
Error
extension.fetch, root fetch with credentials: 'include', extension.attachFrame, attachFrame, cookies.get, permissions.request, setRequestHeaderRule, removeRequestHeaderRule: everything that goes through the extension bridge
The extension marker data-fkn-extension did not appear within 1000 ms plus a 150 ms grace after load, and the install prompt did not produce a usable extension. In a worker realm this arrives as a ReferenceError instead, because MutationObserver and document do not exist there.
Offer promptInstall(reason) or a link to the store listing, then retry once the user has installed or enabled it.
Yes, once the user installs or enables it
The FKN WebExtension is installed but too old for this page: it speaks ABI <abi> and this page needs at least <required>. Updating the extension fixes this.
ExtensionOutdatedError
frame.requestPermissions on the extension, the category form of permissions.request, and the exposure wait every extension call runs
The extension speaks an older ABI than the call needs. Categories need ABI 2, so an extension below it is refused before anything is sent rather than being asked for a row it cannot draw.
Read readExtensionHandshake() first and ask for a category only at abi >= 2 (the handshake carries abi on every status but 'absent'), or catch it and fall back to the per-key { key, scope } form, which every version answers. It is thrown in your own realm, so instanceof works.
Yes, once the user updates the extension
fetch: refusing to forge request header(s): <names>
Error
extension.fetch
The init.headers carried a header outside the forgeable set origin, referer, cookie. <names> is the deduplicated lowercase list.
Send only the three forgeable names through that slot. Every other header is a normal request header and goes through untouched.
No
fetch: a forged Cookie header and credentials:'include' are different identities - send one or the other
Error
extension.fetch
Both a non-empty forged cookie header and credentials: 'include' were passed.
Pick one identity and drop the other. There is deliberately no precedence rule.
No
fetch: could not install the header rule for a forged Cookie, so the request was not sent unauthenticated
Error
extension.fetch with a forged cookie
The session rule that carries the cookie could not be installed, for example because the url is too long for the rule filter. The request is not sent rather than sent without the cookie.
Shorten the url or drop the forged cookie. A failed rule for origin or referer is not fatal: that request runs without the header.
Sometimes, with a shorter url
FKN cloud.fetch: no proxy is available (the relay directory could not be read, and no fallback origin is configured)
Error
cloud.fetch, root fetch on the cloud path
The ranked relay directory produced no proxy origin, and the published build carries no fallback origin.
Retry after a short backoff. The endpoint cache lives 30000 ms and a cooled relay is retried after 60000 ms.
Yes
request rule <id> was not issued to this document
Error
removeRequestHeaderRule(ruleId)
The rule id was never issued to this frame, or was issued for a different rule kind.
Remove only the ids your own setRequestHeaderRule returned.
No
setRequestHeaderRule: refusing to rewrite request header(s): <names>
Error
extension.setRequestHeaderRule, from the first extension store release after 0.1.54
An operation in requestHeaders names a header other than Origin, Referer and Cookie, the three extension.fetch can forge. Host and :authority are refused with the rest, since either would pick another site on the same address. <names> is the deduplicated lowercase list. Thrown before anything is asked, and checked again in the extension's worker.
Rewrite only Origin, Referer or Cookie in a header rule.
No
setRequestHeaderRule: requestHeaders must be an array of header operations
Error
extension.setRequestHeaderRule, from the first extension store release after 0.1.54
requestHeaders is not an array, which only an untyped call can send. Thrown before anything is asked.
Pass an array of { header, operation, value? } operations.
No
Whatever new Request(input, init) throws, typically a TypeError
TypeError
extension.fetch
The extension path normalises through a real Request first, so a GET with a body, or a ReadableStream body without duplex: 'half' on Chromium, throws before anything is sent. The text is the browser's.
Fix the init so the browser accepts it.
No
Permission denied: network on <host> (network.fetchCredentialed <scope>)
PermissionDeniedError, with grantKey, category, site, hosts, permissionKey and scope
extension.fetch, root fetch with credentials: 'include'
The user refused the Network row for that host, or a stored deny covers it. A local target is the same row, with the chip local network.
Match error.name === 'PermissionDeniedError'; neither isLocatorDenied nor isTerminalError matches it. See Permissions and consent.
Yes, if the user changes their mind
cookies.get: url must be an absolute http(s) url
Error
cookies.get
The url did not parse on its own, or its scheme is neither http nor https. A cookie has a host or it has nothing, and a grant row is keyed by hostname.
Pass an absolute http or https url.
No
400 {"error": "fkn-proxy-protocol must be http or https"}
No error: the Response resolves with ok: false
cloud.fetch, and the root fetch on the cloud path
The target url scheme is neither http nor https.
Use an http or https url. Check response.ok and read error from the JSON body.
No
400 {"error": "missing fkn-proxy-hostname"}
No error: the Response resolves with ok: false
cloud.fetch, and the root fetch on the cloud path
No hostname reached the proxy.
Pass an absolute url that carries a hostname.
No
403 {"error": "proxying FKN platform domains is not allowed"}
No error: the Response resolves with ok: false
cloud.fetch, and the root fetch on the cloud path
The proxy keeps its own copy of the platform host list (fkn.app, fkn.dev, dot-anchored). The library and the broker already refuse sdbx.app before the request leaves, so the server list is shorter by one suffix.
Point the request at the app's own api; a platform host is never proxied.
Every resolved address of the target is checked and one was in a private, loopback, link-local, multicast or documentation range. The check runs inside the DNS resolver too, so the dialed address is the validated one.
Target a public address. A host on the local network is reachable through the extension backend, which asks the user for network.fetchLocal.
No
429 {"error": "rate limit exceeded"}
No error: the Response resolves with ok: false
cloud.fetch, and the root fetch on the cloud path
The per-caller request-rate budget for this tier is spent.
Wait, then retry; the budget refills.
Yes, after a wait
429 {"error": "upstream origin is saturated"}
No error: the Response resolves with ok: false
cloud.fetch, and the root fetch on the cloud path
The proxy holds too many concurrent requests to that one upstream origin for this caller.
Retry, and cap how many requests the app keeps open against one origin.
Yes, immediately in most cases
413 {"error": "request body exceeds POST_MAX_BODY_SIZE"}
No error: the Response resolves with ok: false
cloud.fetch, and the root fetch on the cloud path
The request body is larger than the configured cap.
Send a smaller body, or split the upload.
No
502 {"error": "upstream fetch failed: <e>"}
No error: the Response resolves with ok: false
cloud.fetch, and the root fetch on the cloud path
The upstream connection or read failed. <e> is the transport error text.
Retry. Read <e> for what the upstream did.
Yes
A row marked No error describes a call that resolves. Check response.ok and the error field of the body instead. The details are on fetch().
dgram.Socket#send on the data-port path, #setMulticastInterface
An address literal failed to encode into wire bytes.
Pass a valid literal.
No
Cannot set headers after they are sent to the client
Error, code ERR_HTTP_HEADERS_SENT
httpOutgoingMessage#setHeader
The head block already went out.
Set headers before the first write.
No
Cannot render headers after they are sent to the client
Error, code ERR_HTTP_HEADERS_SENT
httpServerResponse#writeHead
writeHead was called twice, or after the head went out. Node merges instead; this throws.
Call writeHead once.
No
Cannot remove headers after they are sent to the client
Error
httpOutgoingMessage#removeHeader
The head block already went out.
Remove headers before the first write.
No
Header name must be a valid HTTP token [<name>]
TypeError, code ERR_INVALID_HTTP_TOKEN
httpOutgoingMessage#setHeader
The name is not a string or fails the HTTP token pattern.
Fix the name.
No
Invalid value "undefined" for header "<name>"
TypeError, code ERR_HTTP_INVALID_HEADER_VALUE
httpOutgoingMessage#setHeader
The value is undefined.
Pass a value or omit the header.
No
Socket is not available
Error, passed to the write callback
httpOutgoingMessage#write, #end
The response is not bound to a socket.
Stop writing once the socket is gone.
No
@fkn/lib: no broker connection within <ms>ms, so <what> could not be requested
BrokerUnreachableError
net.Socket#connect (<what> is an outbound tcp socket), net.Server#listen (a tcp listener), dgram.Socket#bind (a udp socket)
No broker connection exists within the deadline: 8000 ms the first time, 1000 ms after any miss. In a worker realm this is the normal answer until the page calls relayWorker.
net.Socket#connect, net.Server#listen, dgram.Socket#bind, #connect, #send to a hostname
The broker's own dns.lookup returned nothing for the name. The code, errno, syscall and hostname fields are set in the broker realm and are not guaranteed to survive the hop.
Match the message, not the code. Check the name; dns.lookup(hostname) answers the same question directly.
Sometimes, DNS can change
tcp connect to <address>:<port> timed out after 12000ms
Error
net.Socket#connect
The relay did not acknowledge the connect within 12000 ms.
Retry. If the session produced no metadata at all the transport is also marked stalled and the next dial picks another relay.
Yes
The upstream OS error text, for example Connection refused (os error 111)
Error
net.Socket#connect
The relay dialed the target and the kernel refused or reset. The relay's own I/O error string is truncated for the wire and rethrown verbatim in the app realm, so the text is the operating system's; the example is the Linux wording and is illustrative.
Handle it as you would Node's ECONNREFUSED, matching the text: no Node code survives the hop.
The target resolves to a loopback, private, link-local or otherwise non-public address and private-target filtering is on. A loopback target is paired locally first, so this is only reached when no local listener owns the port.
Use a public target, or a local net.Server in the same broker data plane for loopback pairing.
cloud.fs, the hybrid fs and opfs share these errors, including the messages the storage service itself returns:
Message
Name or code
Surfaced by
What happened
What to do
Retryable
storage locked: connect the app again to open encrypted data
StorageLockedError, code FKN_E2E_LOCKED
cloud.fs.readFile, readFileSealed, writeFile, and the same members through promises
The broker holds no usable key for this app. The call raises the unlock card first and waits; this error arrives only after the card is dismissed, when no window can show it, or when the popup delivered no usable key.
Call unlock() or point the user at the FKN card, then retry. The message deliberately does not match /not found/: locked is unreadable, not empty. See Encryption.
Yes, after unlock()
storage: no object at that path, or the original sentence (Not found, storage: read failed (404))
StorageNotFoundError, code FKN_STORAGE_NOT_FOUND
cloud.fs.readFile, readFileSealed, writeFile
There is no object at the path. Absence arrives worded two different ways: the api refusing to presign a path with no committed row, and a presign that succeeded followed by a 404 on the object. Both re-mint to this one class.
Test isNotFound(err) or err.code === STORAGE_NOT_FOUND, never the message. "Nothing here" is safe to overwrite; "I could not tell you" is not.
No
storage: api unreachable
Error (a FKN_API_UNREACHABLE code is set in the data plane and does not reliably survive the hop)
every cloud.fs member
The fetch to the api's GraphQL endpoint threw. Distinct from an answered error on purpose: a caller may relax an obligation on "nobody answered", never on an answered 500.
Match message.startsWith('storage: api unreachable'). Keep the local copy and retry later.
Yes
storage: not connected
Error
every cloud.fs member
No connect token for this scope, so the account is not connected to this site.
every cloud.fs member that lists, reads, writes or removes, and an fs call that waits on the account, such as pull, adopt() or a readFile this device cannot answer
The account this page was on when the call was made is not the one signed in now, so FKN refused the call, or the library refused it before sending, and nothing was read, written or removed. A connection renewed without a switch raises it too.
Test isAccountChanged(err) or err.code === STORAGE_ACCOUNT_CHANGED, read again what the app is acting on, then make the call again.
Yes, as a new call under the account signed in now
storage: the FKN broker cannot pin calls to an account yet, so nothing was sent
every cloud.fs member that lists, reads, writes or removes, and an fs call that waits on the account, such as pull, adopt() or a readFile this device cannot answer
The broker this page reached, or the shared worker behind it, is older than the account pin, so the call was not sent: an unpinned call could land in an account the app did not mean.
Keep the local copy and try again later. It stops once fkn.app has updated.
Yes, once fkn.app has updated
storage: the broker answered no account pin
Error
every cloud.fs member that lists, reads, writes or removes, and an fs call that waits on the account, such as pull, adopt() or a readFile this device cannot answer
The broker answered the request for the account pin with no pin, so the call was not sent.
Try the call again. The next call asks for the pin afresh.
Yes
storage: reserved path
Error
cloud.fs reads, writes and deletes on .fkn or .fkn/*
That prefix is platform-internal.
Use another path.
No
storage: read failed (<status>)
Error
cloud.fs.readFile, readFileSealed
The presigned object fetch answered a non-2xx other than 404. A 404 becomes StorageNotFoundError instead.
Retry.
Yes
storage: write failed (<status>)
Error
cloud.fs.writeFile
The presigned upload PUT answered a non-2xx.
Retry.
Yes
storage query failed: <status>
Error
every cloud.fs member
The GraphQL response was not ok, or carried no data, and no errors array explained it.
Retry.
Yes
Invalid path
Error
cloud.fs reads, writes, deletes, rename
The path is empty, longer than 1024 characters, has more than 64 segments, holds a control character, or has any empty, . or .. segment. A leading slash produces an empty first segment, so /library/catalog.json is refused.
Use a relative path such as library/catalog.json.
No
Storage quota exceeded
Error
cloud.fs.writeFile
The account's byte limit would be passed. Checked at presign and again at commit.
Delete something or upgrade. cloud.fs.quota() reports the headroom.
No, until space is freed
Object limit exceeded
Error
cloud.fs.writeFile
The account holds the maximum number of objects and this write would add one more.
Delete an object first.
No
Object too large
Error
cloud.fs.writeFile
The declared size, or the uploaded object's real size, is over the per-object cap.
Split the file.
No
Concurrent update, retry
Error
cloud.fs.writeFile, unlink
Another writer moved the row between the presign and the commit, or between reading and deleting. The compare-and-set refused.
Retry the whole write.
Yes
Upload not found
Error
cloud.fs.writeFile
The commit named an upload key with no object behind it.
Retry the write from the start.
Yes
Not signed in
Error
every cloud.fs member
The bearer resolved to no live session, or its credential generation is stale. The data plane also reports a connect refusal when it sees this exact sentence.
Call account.login().
Yes, after signing in
Storage is not configured
Error
every cloud.fs member
The api instance has storage disabled.
Report it; the app cannot change this.
No
Not found
Error
cloud.fs.unlink and other members on a .fkn-prefixed or absent row
The row does not exist, or the path starts with the platform prefix. On readFile and writeFile this is re-minted to StorageNotFoundError; unlink does not run the re-mint, so it arrives as a plain Error and isNotFound answers false.
For deletes, match the message, or treat any delete failure as best effort.
No
<CODE>: <text>, <syscall> '<path>'
Error, with code, path and syscall
every node-style member of fs, opfs, cloud.fs
The usual filesystem conditions, for example ENOENT: no such file or directory, stat 'a/b'. Codes in use: ENOENT, EEXIST, ENOTDIR, EISDIR, ENOTEMPTY, ERR_FS_EISDIR, EBUSY, EINVAL.
Branch on err.code exactly as with Node's fs.
Depends on the code
storage: <path> exists but could not be read, retry once its scope is available
Error, code FKN_E2E_LOCKED
fs.readFileSync, statSync, writeFileSync, renameSync, open, stat, rename on an unhydrated path
The path is known from a listing but its bytes could not be read into the memory copy, typically because the account is locked. Such a path still answers exists() and still appears in readdir.
Unlock, then remount(). Deleting it needs no key.
Yes, after unlock()
The "data" argument must be of type string, Buffer, TypedArray, or DataView
TypeError
fs.writeFile, writeFileSync, appendFile, appendFileSync, and the cloud.fs equivalents
A Blob, an object or anything else was passed as the data.
Convert to bytes first. Only a conflict resolver may hand back a Blob.
No
opfs: invalid path
Error
fs.pull(path), fs.readFileSealed(path), and every direct opfs member
The path holds a .. segment, or normalises to nothing. The memory layer never sends .., but pull and readFileSealed pass the raw string.
Normalise the path before calling.
No
fs: cloud unreachable
Error
fs.writeFile, remove, adopt
The cloud state probe answered 'unknown', so nobody could be asked, and there is no local half to queue from. Kept distinct from the row below on purpose.
Retry. The probe costs up to 8000 ms the first time and 1000 ms after a timeout.
Yes
fs: no storage backend available
Error
fs.writeFile, remove
No OPFS in this realm and the account answered a definite sign-out.
Sign in, or accept that this realm has no durable store.
Yes, after signing in
fs: this needs a signed-in account
Error
fs.adopt()
The cloud half answered 'disconnected'.
Call account.login() first.
Yes, after signing in
Only a read or a write turns the locked and not-found rows into a StorageLockedError or a StorageNotFoundError. A cloud.fs.unlink failure is not converted, so isNotFound returns false for it. The node-style members carry Node’s own error.code, listed on storage.
Encryption has three messages of its own, exported as constants from @fkn/lib/messages. The storage service answers with four more on a write:
Message
Name or code
Surfaced by
What happened
What to do
Retryable
fkn:e2e-locked
Error on the wire, re-minted to StorageLockedError in the app realm
cloud.fs reads and writes
The broker holds no usable key for this app. This is the wire form of storage locked, exported as E2E_LOCKED_MESSAGE.
Handle StorageLockedError instead; match this prefix only when reading the raw broker error, as a package does.
Yes
fkn:e2e-stale-epoch: this file is encrypted under a previous key you reset
Error
cloud.fs.readFile, readFileSealed
The object was sealed under a key generation the user has since reset. The old key is gone everywhere. Exported as E2E_STALE_EPOCH_MESSAGE.
Do not retry and do not overwrite blindly. The only way back is an encrypted export taken before the reset. See Encryption.
No, never
fkn:e2e-integrity: stored data failed its integrity check
Error
cloud.fs.readFile, readFileSealed
The row carries no seal marker, an unknown marker, a scope that disagrees with the bearer, or an envelope whose tag did not verify. Exported as E2E_INTEGRITY_MESSAGE.
Never overwrite the path on this error. Surface it and stop.
No
Superseded key
Error
cloud.fs.writeFile
The account's key rotated between sealing the bytes and committing them. The commit names the generation it sealed under, so this is caught rather than silently stored.
Retry the write; it reseals under the current generation.
Yes
This account has no encryption keys yet
Error
cloud.fs.writeFile
The account holds no key record, which is what a settled key reset leaves behind.
Send the user to the security page to enrol again. cloud.fs.encryption() answers enrolled: false in this state.
Yes, after re-enrolling
This account stores sealed objects
Error
cloud.fs.writeFile
A write arrived with no seal marker for an account that stores sealed objects.
Report it; the broker seals every write, so an app cannot produce this on its own.
No
Invalid encryption
Error
cloud.fs.writeFile
The marker on the commit is not one the api recognises.
Report it; the marker is set by the broker, not by the app.
No
Never retry or overwrite after the stale-epoch row (the file was encrypted under a previous key generation) or the integrity row. The reasons are on encryption.
attachFrame, extension.attachFrame and cloud.attachFrame produce these errors, with an iframe, with window, with blank or with storageState. So do goto, evaluate, addScriptTag, clearCookies, on and the liveness checks that every later call on the Frame runs:
Message
Name or code
Surfaced by
What happened
What to do
Retryable
cloud.attachFrame needs a window realm
Error
cloud.attachFrame, and attachFrame when it falls to the cloud backend
No window.
Attach on the main thread.
No
attachFrame: syncCookies was replaced by cookies. true is cookies: 'persistent', or 'native' for the person's own browser cookies on the extension; false is cookies: 'ephemeral'
TypeError
attachFrame, cloud.attachFrame, extension.attachFrame, with an iframe or a window, since @fkn/lib 0.9.42
The options carry a syncCookies key, true, false or undefined alike. The option was renamed and its values split three ways, so the call is refused before anything is attached rather than read under the old meaning.
Replace the key with cookies: 'persistent' for the cloud jar true meant there, 'native' for the person's own browser cookies true meant on the extension, 'ephemeral' for false. See which jar, which backend.
No
attachFrame: cookies must be 'persistent', 'ephemeral' or 'native', not "<value>"
cookies was set to something other than the three values.
Pass 'persistent', 'ephemeral' or 'native', or leave it out for 'persistent'.
No
attachFrame: storageState seeds a fresh jar, so it needs cookies: 'ephemeral'
TypeError
attachFrame, cloud.attachFrame, extension.attachFrame, with an iframe or a window, beside any cookies but 'ephemeral', the default included, since @fkn/lib 0.9.43
A seed belongs to a jar of the attachment's own. On 'persistent' a seeded cookie would land in the jar every app of your top-level site shares, and on 'native' in the person's own browser session. Refused before anything is attached.
Pass cookies: 'ephemeral' beside storageState, or drop storageState. See seeding a fresh jar.
No
attachFrame: storageState.<path> <what is wrong>
TypeError
attachFrame, cloud.attachFrame, extension.attachFrame with storageState and cookies: 'ephemeral', before anything is attached
The first field of another shape, in the order the state lists them, named by its path: a key the shape does not have, a partitioned cookie's partitionKey and an origin's indexedDB among them; a missing or non-string name, value or domain; a name or value with ; or a control character, or a name with =; a domain that is not a host; a path that does not start with /; an expires that is neither -1 nor a positive number; a sameSite other than 'Strict', 'Lax' or 'None'; a cookie a browser would not store, SameSite=None or a __Secure- name without secure: true, or a __Host- name without secure: true, a host-only domain and path /; an origin that is not an absolute http or https origin; a localStorage item that is not { name, value } strings. A state that is not an object reads attachFrame: storageState must be an object { cookies?, origins? }. The two rows around this one share its prefix.
Fix the field the path names, then attach again. A state Playwright saved converts as in seeding a fresh jar.
No
attachFrame: storageState.cookies[<i>].expires is milliseconds since the epoch, and <value> reads as a date before 1974; a Playwright storageState gives seconds, so multiply it by 1000
TypeError
attachFrame, cloud.attachFrame, extension.attachFrame with storageState and cookies: 'ephemeral', before anything is attached
A cookie's expires is above 0 and below 100000000000. Playwright's storageState gives seconds and FKN's takes milliseconds, so a state saved by Playwright and passed unchanged meets this: as milliseconds it is a date before 1974, as seconds a date of today. Checked once every field has its shape, so a row above comes first.
Multiply each expires by 1000, and keep -1 as it is for a session cookie.
No
attachFrame: lockdown runs on the extension, which serves only cookies: 'native'
TypeError
attachFrame, cloud.attachFrame and extension.attachFrame with lockdown: true beside any cookies but 'native', the default included
lockdown is a header policy the extension installs, so it runs on the extension backend only, and the extension serves only the person's own browser cookies.
Pass cookies: 'native' beside lockdown, through attachFrame or extension.attachFrame, or drop lockdown.
No
cloud.attachFrame: cookies: 'native' needs the FKN browser extension; the cloud render proxy has no browser cookies
TypeError
cloud.attachFrame with cookies: 'native'
The render proxy runs on FKN's own jars, so it has no copy of the person's browser cookies to serve.
Call the root attachFrame or extension.attachFrame for 'native', or attach on the cloud with 'persistent' or 'ephemeral'.
No
attachFrame: cookies: '<value>' runs on the cloud render proxy; call cloud.attachFrame or the top-level attachFrame
extension.attachFrame with cookies: 'persistent' or 'ephemeral', so also with no cookies at all
The extension serves only cookies: 'native'. The default is 'persistent', so an extension.attachFrame call written before 0.9.42 meets this. Refused before any exposure wait, with the ABI the extension reported, 0 when none.
Pass cookies: 'native' for the person's own browser cookies, or call cloud.attachFrame or the root attachFrame for the other two.
No
cloud.attachFrame: the iframe must be connected to the document before attaching
Error
cloud.attachFrame
The iframe is not in the document.
Append the iframe first.
Yes, after appending it
cloud.attachFrame: the iframe's sandbox attribute must include <tokens> for the render proxy to load
Error
cloud.attachFrame
A sandbox attribute is present without allow-scripts and allow-same-origin. No sandbox attribute at all is fine.
Add the tokens or remove the attribute.
No
cloud.attachFrame: the iframe src is not a valid URL: <src>
Error
cloud.attachFrame
The src attribute is present but does not resolve to a URL.
Fix the src, or leave it empty and use goto.
No
cloud.attachFrame: this iframe is already attached; navigate with the Frame returned by that attach or use a fresh iframe
Error
cloud.attachFrame
The src already points at the render proxy page.
Reuse the Frame from the first attach, or use a fresh iframe.
No
attachFrame: refusing to target the extension's own pages or an FKN platform origin
Error
cloud.attachFrame, extension.attachFrame, attachFrame, with an iframe or a window, and goto on either
The target is an extension page or a platform host. Checked page-side for a fast error and again authoritatively in the content script and the render proxy page. For a window on cookies: 'native', from the first extension store release after 0.1.54, the extension's worker checks the window it adopts as well, and closes one found on such a page.
Point the frame elsewhere.
No
<operation>: could not install the rule that keeps an attached site off FKN platform domains, so nothing was done: <reason>
Error
attachFrame on cookies: 'native', extension.attachFrame and extension frame.goto, from the first extension store release after 0.1.54
Before the extension hands an attached frame to your app, and before a goto() navigates it, it installs browser rules in your app's tab that block the attached site's requests to FKN platform hosts. The browser refused them, so the attach or the navigation stops rather than going on without the rules. <operation> is attachFrame or frame.goto, and <reason> is the browser's own message.
Retry the call. When it keeps failing, reload the page, which retires the rules the earlier documents in the tab held.
Sometimes
cloud.attachFrame: the iframe window is not available
Error
cloud.attachFrame
contentWindow is null right after src was set.
Retry with a fresh iframe.
Yes
cloud.attachFrame: timed out connecting to the render proxy page
Error
cloud.attachFrame
The handshake with the render proxy page did not connect within 20000 ms. On failure the iframe's referrerPolicy, allow and src are restored.
Retry.
Yes
cloud.attachFrame: the render proxy never became ready
Error
cloud.attachFrame
The render proxy page connected but did not report ready within 65000 ms.
Retry.
Yes
attachFrame: blank must be an object { url }
TypeError
attachFrame and cloud.attachFrame with blank
blank was null, a string or another non-object.
Pass blank: { url: 'https://example.org/' }.
No
attachFrame: blank takes { url }, not "<key>"
TypeError
attachFrame and cloud.attachFrame with blank
blank carried a key other than url.
Pass url alone.
No
attachFrame: blank.url must be an absolute http or https url, not "<value>"
TypeError
attachFrame and cloud.attachFrame with blank
blank.url is not a string, does not parse as an absolute url, or has another scheme.
Pass the absolute http or https url the empty page is presented at.
No
attachFrame: blank needs an iframe with no src; this one has "<src>"
TypeError
attachFrame and cloud.attachFrame with blank
The iframe already has a src other than about:blank, so the page it loads would race the empty one.
Attach a fresh iframe with no src.
No
attachFrame: blank does not combine with lockdown
TypeError
the root attachFrame with blank, lockdown and cookies: 'native'; every other combination meets the lockdown or 'native' refusal above first
lockdown is the extension's header policy, and a blank page is served by the cloud render proxy alone.
Drop one of them.
No
attachFrame: blank runs on the cloud render proxy, which has no browser cookies; pass cookies: 'persistent' or 'ephemeral'
TypeError
the root attachFrame with blank and cookies: 'native', before any exposure wait
Only the render proxy can present an origin without loading it, and it never runs on the person's own browser cookies.
Pass cookies: 'persistent' or 'ephemeral', or leave cookies out.
No
attachFrame: a blank page is served by the cloud render proxy, which this build does not configure
Error
attachFrame and cloud.attachFrame with blank
This build of @fkn/lib has no render proxy origin configured, so there is nowhere to serve the empty page from.
Use a published build of @fkn/lib, which configures one.
No
attachFrame: blank does not apply to a window
TypeError
attachFrame, cloud.attachFrame, extension.attachFrame with window and blank
A window opened with no url is already called blank, so the option would mean two things in one call.
Attach an iframe for blank, or open the window with window: {} and goto it.
No
attachFrame: a blank page is served by the cloud render proxy only; call cloud.attachFrame
extension.attachFrame with blank and cookies: 'native'
The extension cannot present an origin without loading it, so no extension build serves a blank page. Refused before anything is dispatched.
Call cloud.attachFrame, or the root attachFrame without cookies: 'native'.
No
cloud.attachFrame: this FKN page predates blank pages; reload the app to load the current one
LocatorUnsupportedError (terminal)
attachFrame and cloud.attachFrame with blank, after the handshake
The fkn.app page that answered, served from the browser's cache, is older than blank pages. The iframe is put back on about:blank, and nothing was requested from the site.
Reload the app, which loads the current page, then attach again.
Yes, after a reload
attachFrame: this FKN page predates storageState; reload the app to load the current one
LocatorUnsupportedError (terminal)
attachFrame and cloud.attachFrame with storageState, after the handshake
The fkn.app page that answered, served from the browser's cache, or the render proxy behind it, is older than storageState. The iframe is put back on about:blank, or the window is closed, and nothing was requested from the site.
Reload the app, which loads the current page, then attach again.
Yes, after a reload
attachFrame: the render proxy did not take the storageState seed
TimeoutError
attachFrame and cloud.attachFrame with storageState
The render proxy did not confirm the seed within 20000 ms. The iframe is put back on about:blank, or the window is closed, and the page at the attach url was never requested.
Attach again.
Yes
mirage: the frame host did not answer the localStorage seed, so it may predate seeding
Error
attachFrame and cloud.attachFrame with blank and a storageState whose origins carry localStorage items
The render proxy's frame host serving the blank page's origin, already mounted when the seed arrives, is older than seeding and did not confirm the localStorage items within 5000 ms. The iframe is put back on about:blank. With a url instead of blank, the same message arrives after the [frame host failed: ](#frame-host-failed) prefix, on the first goto.
Reload the app, which loads the current render proxy, then attach again.
Yes, after a reload
attachFrame: lockdown needs domains when the frame already has a src; pass domains or load it via goto
Error
extension.attachFrame, attachFrame with lockdown and cookies: 'native'
A headers-based lockdown cannot cover a document that is already loading.
Pass domains, or attach a blank iframe and goto.
No
attachFrame: the iframe must be connected to the document before attaching
Error
extension.attachFrame, attachFrame with cookies: 'native'
The iframe is not in the document. Checked after the exposure wait, so a missing extension is reported first.
Append the iframe first.
Yes
frame: no iframe registered for marker="<marker>"
Error
extension.attachFrame
The CustomEvent carrying the attach marker never reached the content script.
Retry the attach.
Yes
The FKN WebExtension is not installed, enabled or not exposed on this page.
Error
extension.attachFrame and attachFrame with cookies: 'native'
The exposure wait runs before the connected-iframe check, so a missing extension is reported first. ExtensionOutdatedError is the other outcome of the same wait, though the wait itself still accepts every ABI; what raises it is a category ask, which needs ABI 2.
Offer promptInstall(reason) or a link to the store listing, as for the fetch row of the same name.
Yes, once installed
attachFrame: the browser did not open the window. Call attachFrame directly in a click or key handler, and allow popups for this page
FrameWindowBlockedError
attachFrame and cloud.attachFrame with window, and attachFrame and extension.attachFrame with window and cookies: 'native' from the first extension store release after 0.1.54
window.open returned null, so nothing opened: the call ran without user activation (an await before it, or no click at all), the popup blocker refused it, or the calling frame is sandboxed without allow-popups. A class, minted in your realm.
Call attachFrame first in the click or key handler, with no await before it, and ask the user to allow popups for the page. See frames.
Yes, from a fresh click
attachFrame: the window refused to attach (<reason>), and loaded nothing
FrameWindowRefusedError
attachFrame and cloud.attachFrame with window
The window opened and refused, naming reason: 'jar-unreachable' when the cookie jar of the app that opened it could not be reached, 'bad-url' when it would not open the address, 'unknown' for a reason newer than this library. The window stays open and shows why.
Branch on error.reason. For 'jar-unreachable', retry from a fresh click, and keep the hidden fkn.app iframe the library adds to your page in the document while the window is open: it is how the window reaches the jar.
Sometimes: 'jar-unreachable' from a fresh click, never 'bad-url'
attachFrame: this page has an opaque origin, which a window can never answer, so no window was opened
FrameWindowRefusedError
attachFrame and cloud.attachFrame with window
reason is 'opaque-opener': the page is a file: document or a frame sandboxed without allow-same-origin, so its origin reads 'null' and nothing can address it. Checked before window.open.
Open the window from a page with a real origin, or add allow-same-origin to the sandbox.
No
attachFrame: a window on cookies: 'native' needs the FKN WebExtension, which is not on this page
ExtensionOperationUnsupportedError, operation 'attachWindow', abi 0
attachFrame and extension.attachFrame with window and cookies: 'native', since @fkn/lib 0.9.47
A window on the person's own browser cookies is a real browser window the extension adopts, and no extension has announced itself on this page. The handshake is read once, with no exposure wait and no install prompt, before anything opens, so the click's activation is left unused.
Open the window from the same click with cookies: 'persistent', the default, or 'ephemeral', which run on the cloud backend whatever is installed, or offer promptInstall(reason) for a later click. See a window on the extension.
No, but the same click can still open one on another value
extension.attachFrame: the window this page opened was not found; it may have closed
Error
attachFrame and extension.attachFrame with window and cookies: 'native', from the first extension store release after 0.1.54
The window opened, but the extension's worker never heard of it within 2000 ms, or it was already gone: the person closed it at once, or the worker was installed moments ago and had never run, which drops the browser's report of a new window. The page closes the window before rejecting.
Attach a fresh window from a new click. On a fresh install, the next attach is adopted.
Yes, from a fresh click
attachFrame: could not install the rule that keeps an attached site off FKN platform domains, so the window was closed: <reason>
Error
attachFrame and extension.attachFrame with window and cookies: 'native', from the first extension store release after 0.1.54
Before the extension hands the window to your app it installs browser rules on the window's tab that block the attached site's requests to FKN platform hosts, the same rules an attached iframe gets. The browser refused them, so the extension closed the window rather than hand it over without them. <reason> is the browser's own message.
Attach a fresh window from a new click. When it keeps failing, reload the page.
Sometimes
attachFrame: the installed FKN WebExtension (ABI <abi>) may predate lockdown, and one that does attaches the frame with its CSP removed rather than locked down. Updating the extension fixes this.
ExtensionOperationUnsupportedError
extension.attachFrame and attachFrame with lockdown: true, since @fkn/lib 0.9.39
The installed extension reports an ABI below 1, the first that serves lockdown, or reports none, and one that old drops the option and attaches the frame with its CSP removed. @fkn/lib refuses before anything is attached, with operation'lockdown' and the ABI the extension reported, 0 when it reports none. A page with no extension at all never reaches this: it takes the install path.
Ask the person to update the FKN WebExtension: every build the stores serve carries lockdown, so this is an install that never updated. Attaching without lockdown still works on it, with the CSP removed.
Yes, once the extension is updated
cloud.attachFrame: timed out connecting to the window
Error
attachFrame and cloud.attachFrame with window
The window's channel did not connect within 60000 ms. The window is closed.
Attach a fresh window from a new click.
Yes, from a fresh click
cloud.attachFrame: the window never became ready
Error
attachFrame and cloud.attachFrame with window
The window connected but its render proxy did not report ready within 65000 ms. The window is closed.
Attach a fresh window from a new click.
Yes, from a fresh click
cloud.attachFrame: the window closed before it connected. Either the user closed it, or this page's Cross-Origin-Opener-Policy cut its link to the window
LocatorUnsupportedError
attachFrame and cloud.attachFrame with window
The window read as closed before its channel connected. A page served with Cross-Origin-Opener-Policy: same-origin loses its window the moment it opens, so this is also what such a page always gets. Terminal.
Serve the page with Cross-Origin-Opener-Policy: same-origin-allow-popups or none. Otherwise the user closed it, which is an answer.
Only after the header changes, or from a fresh click
cloud.attachFrame: the window went away before it connected; attach a fresh window
LocatorUnsupportedError
attachFrame and cloud.attachFrame with window
The window told this page it was going, before its channel connected: it was closed or navigated away. Terminal.
Attach a fresh window from a new click.
Yes, from a fresh click
cloud.attachFrame: the attached window closed, reloaded or left the page FKN attached; attach a fresh window
LocatorUnsupportedError
every call on a WindowFrame once closed has resolved, and an attach whose window closed after connecting
The attachment ended: close() ran, the user closed the window, it reloaded or navigated off the attach page, it lost the app's cookie jar, or the app page went away. The name is set so the dispatch loop stops instead of retrying a dead channel.
Attach a fresh window. Read closed to learn the end without a failing call.
No, this attachment is over
cloud.attachFrame: the attached window stopped answering; attach a fresh window
LocatorUnsupportedError
every call on a WindowFrame after two missed heartbeats
The window missed two heartbeats in a row, 2000 ms apart, while this page was visible: it crashed or froze. The attachment ended and closed resolved.
Attach a fresh window.
No, this attachment is over
extension.attachFrame: the attached window closed; attach a fresh window
LocatorUnsupportedError
every call on a WindowFrame of cookies: 'native' once closed has resolved, and one in flight when it did, from the first extension store release after 0.1.54
The attachment ended: close() ran, anyone closed the window, its page went to an FKN host (a goto that redirected there ends with this), or your page went away. A reload or a navigation in the window does not end it, since the Frame follows the window's page. The name is set so the dispatch loop stops instead of retrying.
Attach a fresh window. Read closed to learn the end without a failing call.
No, this attachment is over
frame.postMessage: this window's page no longer keeps a link to the app's page, so a message cannot reach it
LocatorDeniedError
postMessage on a WindowFrame of cookies: 'native', from the first extension store release after 0.1.54
The page in the window was served with a Cross-Origin-Opener-Policy (same-origin or same-origin-allow-popups), which cut the window's link to your page as it loaded, so neither side's messages can reach the other. Every other call still answers: the extension knows the window by its tab, never by that link.
Read the page through locators or evaluate instead, which do not need the link. A message cannot cross while the link stays cut.
No
frame.goto: this window is not attached to this page
Error
goto on a WindowFrame of cookies: 'native', from the first extension store release after 0.1.54
The extension's worker holds no record of this window for the calling page: it closed or reached an FKN host while the goto was on its way, so the attachment is ending.
Wait on closed, then attach a fresh window.
No, this attachment is over
frame.goto: a window only shows an http or https address
TypeError
goto on a WindowFrame of cookies: 'native', from the first extension store release after 0.1.54
The goto target resolved to another scheme, such as about:blank or data:. The window does not move.
Pass an http or https address.
No
attachFrame: pass either iframe or window, not both
Pass { url?, width?, height? }, or {} for a blank window.
No
attachFrame: window.url must be a string
TypeError
attachFrame, cloud.attachFrame, extension.attachFrame with window
window.url was set to something other than a string.
Pass the address as a string.
No
attachFrame: window.url is not a valid URL: <url>
TypeError
attachFrame, cloud.attachFrame, extension.attachFrame with window
window.url does not resolve against the page's location.href.
Pass an absolute url.
No
attachFrame: window.url must be an http or https address
TypeError
attachFrame, cloud.attachFrame, extension.attachFrame with window
The address resolved to another scheme. A window only ever shows a website.
Pass an http or https address, or omit url and goto one later.
No
attachFrame: window.<width or height> must be a positive number of CSS pixels
TypeError
attachFrame, cloud.attachFrame, extension.attachFrame with window
window.width or window.height was not a finite number above 0.
Pass a positive number, or omit it for the default of 500 by 700.
No
frame.goto: waitUntil must be 'load' or 'commit', not "<value>"
TypeError
frame.goto on either backend, since @fkn/lib 0.9.42
waitUntil was another value. 'documentstart' is now 'commit', and Playwright's 'domcontentloaded' and 'networkidle' are not served. Thrown before anything is sent, so the frame does not move.
Pass 'commit' where you passed 'documentstart', or leave waitUntil out for 'load'.
No
frame.goto: pass timeout, in milliseconds; timeoutMs is not an option
TypeError
frame.goto
The options carry a timeoutMs key, the name the wire uses. The public option is timeout, as in Playwright.
Rename the key to timeout.
No
frame.goto: timeout must be a number of milliseconds
TypeError
frame.goto
timeout is not a number.
Pass a positive number of milliseconds, or leave it out for 30000.
No
frame goto: "<url>" is not a url this page can resolve
Error
extension frame.goto
The url does not resolve against the app page's location.href.
Pass an absolute url.
No
frame load failed for <href>
Error
extension frame.goto with the default waitUntil: 'load', on an iframe or a window
The iframe fired error, or for a window the browser reported the navigation failed. A load another one replaced is not a failure: an abort is ignored, since Firefox reports one before each http load it retries after trying https first.
Retry, or check the target.
Yes
frame load timed out after <ms>ms for <href>
TimeoutError
extension frame.goto with the default waitUntil: 'load', on an iframe or a window
No load within the goto's timeout, 30000 ms by default. The extension's message, read back in your realm as a TimeoutError, since @fkn/lib 0.9.42.
Raise timeout, or retry.
Yes
frame goto: documentstart timed out
TimeoutError
extension frame.goto with waitUntil: 'commit', on an iframe or a window
The new document's content script did not announce itself within the goto's timeout, or for a window no new document committed in it. A 'commit' goto travels to the extension under the name it has always read there, so its deadline keeps that name in the message.
Raise timeout, or retry.
Yes
cloud.attachFrame: the render proxy did not answer goto; the frame may have been detached or its page reloaded
TimeoutError
cloud frame.goto
The render proxy page did not answer within the goto's timeout (30000 ms by default) plus 5000 ms.
Check the frame is still attached, then retry with a fresh attach.
Sometimes
navigation to <url> did not commit; the frame is still blank
Error
cloud frame.goto with the default waitUntil: 'load'
The render proxy navigated but no document committed.
Retry.
Yes
navigation to <url> did not commit a document within <ms>ms
TimeoutError
cloud frame.goto with waitUntil: 'commit'
No document of the goto's own held the frame within its timeout: the navigation failed, or loaded something that is not a page. The render proxy's message, read back as a TimeoutError.
Check the target loads, then retry.
Yes
frame host failed: <the frame host's message>
Error
cloud frame.goto, and attachFrame with storageState and a url, whose first page is loaded as a goto
The render proxy's frame host, the document that serves the target origin's pages, failed before it took the navigation, so the goto ends at once rather than at its timeout. Its message follows the prefix: for a host from before seeding, which never answers the localStorage part of a storageState within 5000 ms, mirage: the frame host did not answer the localStorage seed, so it may predate seeding. With blank, whose host is already mounted when the seed arrives, the attach itself rejects with [that message alone](#mirage-the-frame-host-did-not-answer-the-localstorage-seed).
Reload the app, which loads the current render proxy, then retry.
Yes, after a reload
frame load timed out after <ms>ms for <url>
TimeoutError
cloud frame.goto with the default waitUntil: 'load'
The render proxy's own load deadline, the goto's timeout, passed.
The user refused the navigation. embed.open is severity 0 and audit only, so its row names no site and the message drops the on <host> half.
Match error.name === 'PermissionDeniedError' and explain the refusal. See Permissions and consent.
Yes
cloud.attachFrame: the attached iframe left the document or was reloaded; attach a fresh iframe
LocatorUnsupportedError
every locator call and goto on a cloud Frame
The iframe was detached, an ancestor was removed, or contentWindow changed. Only the direct parent is watched; an ancestor removal is caught on the next call. The name is set deliberately so the dispatch loop stops instead of retrying a dead channel.
Attach a fresh iframe.
No, this attachment is over
frame: this frame no longer holds the document the app attached it to
LocatorDeniedError (terminal)
every locator call on an extension Frame
The framed document navigated somewhere the app never declared. The message deliberately does not say where it went.
goto a declared target again. The policy travels down every frameLocator hop.
No
frame: navigate the frame before <operation>
LocatorDeniedError (terminal)
every gated call on a blank frame, on both backends
The frame still holds no document, so there is no site to consult and no host to name on a row. An attach with no domains and no goto lands here on the first gated call.
goto a target first, or declare domains at attach.
No
frame: the document changed under this call
LocatorError
every gated cloud locator call
The realm that was about to run the operation read its own url and found a different site from the one the middle page picked a grant row for. The check is atomic with the call, so a document that moved mid-call cannot be served under the old row.
Let the dispatch loop retry; no handling is needed.
Yes, to the deadline
frame: <operation> is only available on an attached frame
LocatorDeniedError (terminal)
frame.fetch on a frame that is not an attachment
fetch is refused outright on a frame that is not an attachment.
Use the Frame returned by attachFrame.
No
frame.evaluate: the code did not settle within 30000ms
TimeoutError
frame.evaluate on either backend
The code had 30000 ms to settle, counted after any consent card, and did not. Only the wait ends: code that never settles keeps running in the page. A TimeoutError on both backends since @fkn/lib 0.9.42.
Return sooner, or split the work into calls that each settle.
Yes
The FKN WebExtension does not support "<operation>" (ABI <abi>). Updating the extension may add it.
ExtensionOperationUnsupportedError
extension frame.evaluate, on an extension below ABI 3, with operation'evaluate'; and attachFrame and extension.attachFrame with window and cookies: 'native', on an extension that does not announce attachWindow (every store build through 0.1.54), with operation'attachWindow', since @fkn/lib 0.9.47
The installed extension does not announce the operation, so it would not run the code or open the window. A window is refused before anything opens, with no wait and no install prompt, so the click's activation is left for your fallback. This is the class's default wording; every other construction passes a message of its own, each with its row on this page.
Ask the person to update the extension. For evaluate, attach on the cloud backend, which runs it on every version; for a window, open it from the same click on cookies: 'persistent' or 'ephemeral'.
Yes, once the extension is updated
frame.addScriptTag: options must be an object { content }
TypeError
frame.addScriptTag, before anything is sent
The argument was not a plain object.
Pass { content: '...' }.
No
frame.addScriptTag: "<key>" is not served; it runs content as a classic inline script
TypeError
frame.addScriptTag, before anything is sent
One of Playwright's url, path or type was passed. The script is always the content you pass, run as a classic inline script.
Pass the source as content, and drop the key.
No
frame.addScriptTag: unknown option "<key>"
TypeError
frame.addScriptTag, before anything is sent
A key other than content and sourceUrl.
Drop the key.
No
frame.addScriptTag: content must be a string
TypeError
frame.addScriptTag, before anything is sent
content is missing or not a string.
Pass the script source as a string.
No
frame.addScriptTag: sourceUrl must be a non-empty string with no line break
TypeError
frame.addScriptTag, before anything is sent
sourceUrl is empty, not a string, or holds a line terminator that would end its //# sourceURL comment early.
Pass a one-line name, or leave sourceUrl out.
No
frame.addScriptTag: the call arrived without an Evaluation grant, so it was not run
LocatorDeniedError (terminal)
cloud frame.addScriptTag
The render proxy found no proof of an Evaluation grant on the call, which is what an fkn.app page that predates addScriptTag sends. The script did not run.
Reload the app, which loads the current page, then call again.
Yes, after a reload
frame.addScriptTag: this render proxy predates addScriptTag; reload the app
LocatorUnsupportedError (terminal)
cloud frame.addScriptTag
The render proxy that answered is older than the operation. The script did not run.
Reload the app, then call again.
Yes, after a reload
frame.addScriptTag: no answer within 30000ms; the script may or may not have run
TimeoutError
cloud frame.addScriptTag
The render proxy did not answer within 30000 ms, counted after any consent card. Whether the script ran is unknown.
Make the script idempotent before calling again, since it may already have run.
Yes, with an idempotent script
frame.addScriptTag: an extension frame does not run scripts through mirage yet; attach with cloud.attachFrame
No extension build serves the operation yet. Refused before anything is dispatched.
Attach on the cloud backend for addScriptTag, or run the code with frame.evaluate, which the extension serves.
No
frame.clearCookies: options must be an object
TypeError
frame.clearCookies, before anything is sent
The argument was neither undefined nor a plain object.
Pass { name?, domain?, path? }, or nothing to clear every cookie the attachment reaches.
No
frame.clearCookies: unknown option "<key>"
TypeError
frame.clearCookies, before anything is sent
A key other than name, domain and path.
Drop the key.
No
frame.clearCookies: <key> is a RegExp, and clearCookies takes strings for name, domain and path for now
TypeError
frame.clearCookies, before anything is sent
Playwright's clearCookies also takes a RegExp, which FKN refuses for now: a pattern would be tested in the render proxy against cookies the app cannot read, so its running time could say which of them exist. A RegExp from another realm is refused too.
Pass the exact string, once per value to remove.
No
frame.clearCookies: <key> must be a string
TypeError
frame.clearCookies, before anything is sent
name, domain or path was set to something other than a string or undefined.
Pass a string, or leave the option out to match every value.
No
frame.clearCookies: <key> must not be empty; leave it out to match every <key>
TypeError
frame.clearCookies, before anything is sent
An empty string. Playwright reads one as no filter and clears every cookie, so a computed empty name would empty the jar; FKN refuses it instead.
Leave the option out to match every value, or pass the value you mean.
No
frame.clearCookies: this attachment reaches no site yet; attach a url, goto one, or declare it in domains
LocatorDeniedError (terminal)
cloud frame.clearCookies
The attachment has no attach url, no goto target and no domains, so it reaches no site whose cookies it could remove.
goto the site, or declare it in domains at attach.
No
frame.clearCookies: <domain> is not on a site this attachment reaches; goto it or declare it in domains
LocatorDeniedError (terminal)
cloud frame.clearCookies with a domain
The domain string is not on the registered domain of any host the attachment reaches: the attach url's, a goto target's, or a declared one. Playwright would match nothing silently; FKN refuses, so a sign-out naming the wrong host fails loudly instead of leaving the session.
Name a domain on one of those sites, or declare the site in domains.
No
frame.clearCookies: this FKN page predates clearCookies; reload the app to load the current one
LocatorUnsupportedError (terminal)
cloud frame.clearCookies
The fkn.app page that answered, served from the browser's cache, is older than the operation. Nothing was sent.
Reload the app, then call again.
Yes, after a reload
frame.clearCookies: this render proxy predates clearCookies; reload the app
LocatorUnsupportedError (terminal)
cloud frame.clearCookies
The render proxy behind the page is older than the operation. Nothing was removed.
Reload the app, then call again.
Yes, after a reload
frame.clearCookies: the cookies are gone from this attachment, but its jar did not take the removal, so other attachments may still send them; call it again
Error
cloud frame.clearCookies
The cookies left this attachment, and the write that commits the removal to the jar failed, so another attachment on the same jar, and one created later, may still carry them.
Call it again: a repeat is harmless.
Yes
frame.clearCookies: the render proxy did not answer within 30000ms; the cookies may or may not be gone, and calling it again is safe
TimeoutError
cloud frame.clearCookies
No answer within 30000 ms. Whether the cookies were removed is unknown.
Call it again.
Yes
frame.clearCookies: an extension frame runs on the browser's own cookies (cookies: 'native'), which FKN does not clear; attach with cookies: 'persistent' to clear the app's jar
An extension frame runs on the person's own browser session, which belongs to them and not to the app. Refused before anything is dispatched.
Attach with cookies: 'persistent' for a session the app can end, or leave the browser's own cookies to the person.
No
frame.on: the event type must be 'message' or 'document', not "<type>"
TypeError
frame.on, frame.off
The event type is neither of the two a Frame reports.
Pass 'message' or 'document'.
No
frame.on: the listener must be a function
TypeError
frame.on
The second argument is not a function.
Pass a function.
No
frame.<method>: this backend cannot <what the method does>
Error
requestPermissions, evaluate, addScriptTag, clearCookies, postMessage and on on a Frame that createFrame built from a backend lacking that method
The backend handed to createFrame does not implement the method, so the Frame refuses it by name rather than answering as if it ran. The Frames attachFrame returns implement every one of them, refusing what a backend does not serve with that backend's own message.
Implement the method on the backend, or do not call it.
No
What each backend (the cloud, the extension or the desktop) refuses is on frames, and how long goto waits is on timeouts.
The chain resolved to nothing. Each attempt fails with a LocatorError and is retried; at the deadline the call rejects with a TimeoutError carrying this message, the last attempt's LocatorError as its cause.
Widen the selector, or raise timeout.
Yes, to the deadline
Strict mode violation: locator resolved to <n> elements
LocatorError, and at the deadline a TimeoutError carrying it
the same operations
The chain matched more than one element and the operation needs exactly one.
Narrow the selector. count and exists accept any number.
Yes, to the deadline
Strict mode violation: expected <name>, got <tag>
LocatorError, and at the deadline a TimeoutError carrying it
videoElement and other tag-pinned operations
The single match is the wrong element type.
Fix the selector.
Yes
Locator timeout (<ms>ms): <operation>
TimeoutError
any operation
The deadline passed with no attempt having failed first, for example a single attempt that hung. A TimeoutError with no cause, since @fkn/lib 0.9.42.
Raise timeout, or find out why the attempt hangs.
No
fill: element <tag> is not fillable
Error, and at the deadline a TimeoutError carrying it
fill
The element is not an <input>, <textarea> or contenteditable node.
Target a real field.
Yes, pointlessly
<operation>: timeout must be a positive number of milliseconds, not <timeout>; every call ends at its deadline, so 0 does not turn it off
TypeError
every action and frame.goto, before anything is gated or sent, since @fkn/lib 0.9.42
timeout was 0, below 0, or not finite. Playwright reads 0 as no deadline; here every call ends by its deadline, so 0 is refused rather than failing at once or waiting forever.
Pass a positive number of milliseconds, or leave timeout out for 30000.
No
<operation>: position is Playwright's offset in pixels, which no backend serves yet; pass relativePosition, a 0..1 fraction of the box
TypeError
click and hover with a position option, before anything is sent, since @fkn/lib 0.9.42
position was the name of the 0..1 fraction of the box before 0.9.42. Playwright's position is in pixels, which no backend serves yet, so the key is refused rather than read as a fraction.
Rename it to relativePosition, with the same values.
No
frameLocator.owner: this frame was not entered through frameLocator(selector)
TypeError
owner() on a FrameLocator
owner() is the Locator on the iframe a frameLocator(selector) entered, and this chain did not end in one.
Call owner() straight after frameLocator(selector), or locate the iframe with locator(selector).
No
locator.contentFrame: only a locator that ends in locator(selector) can enter its frame; use frameLocator(selector)
TypeError
contentFrame() on a Locator
contentFrame() enters the iframe a trailing locator(selector) matches, and this chain ends in another selector.
Call frameLocator(selector) on the chain instead.
No
frameLocator: no <iframe> matched
LocatorError
any call whose chain crosses a frameLocator
The barrier selector matched nothing.
Wait for the iframe, or fix the selector.
Yes, to the deadline
frameLocator: expected <iframe>, got <<tag>>
LocatorError
any call whose chain crosses a frameLocator
The barrier's first match is not an iframe.
Fix the selector.
Yes
frameLocator: this realm has no bridge into child frames
LocatorUnsupportedError, operation 'frameLocator'
any call whose chain crosses a frameLocator
The realm cannot descend.
Do not descend from this realm; drive the child from a realm that has a bridge.
No
frameLocator: <iframe> has no contentWindow
Error
any call whose chain crosses a frameLocator
The iframe holds no window yet.
Wait for it to load.
Yes
the frame this call was routed to went away mid-call, retrying
LocatorError
any call across a frameLocator hop
The child frame navigated mid-call.
Let the dispatch loop retry; no handling is needed.
Yes, to the deadline
the attached frame became stale during the call, retrying
LocatorError
any call on an extension Frame
The attached frame navigated mid-call.
Let the dispatch loop retry; no handling is needed.
Yes
frame-locator: the target frame has no window yet, frame-locator: registration <id> went away before it answered, retrying
LocatorError
any call across a window bridge
Bridge registration raced the call.
Let the dispatch loop retry; no handling is needed.
a chain built with a selector this stack does not register
The selector module is not in the registry. @fkn/lib's Locator is pre-bound to the extension stack's registry, which adds videoElement and the reason option.
The index is out of range on the rebuilt TimeRanges.
Check length first.
No
fetch: url must be a string
LocatorInvalidError, operation 'fetch'
frame.fetch on either backend
The url argument is not a string.
Pass a string.
No
fetch: not a valid url: <url>
LocatorInvalidError, operation 'fetch'
frame.fetch
The url does not resolve against the landing document's baseURI.
Pass a valid url.
No
frame.fetch on the cloud backend needs its own session: attach with cookies: 'ephemeral'
LocatorDeniedError (terminal)
cloud frame.fetch, and cloud ensure('fetch'), on cookies: 'persistent', the default
The attachment uses the shared render proxy jar, one record shared by every app of the top-level site. No grant can lift this.
Re-attach with cookies: 'ephemeral'.
No
frame.fetch: url must be a string
LocatorDeniedError (terminal)
cloud frame.fetch, and cloud ensure('fetch')
The page-side gate sees a non-string where the url should be. ensure('fetch') hits this because the gate reads the options object as the url.
Pass a url, or skip ensure('fetch') on the cloud backend.
No
frame.fetch on the cloud backend needs an absolute url
LocatorDeniedError (terminal)
cloud frame.fetch
A relative url resolves in the landing realm, which the app page cannot know.
Pass an absolute url.
No
frame.fetch: only http(s) urls are supported
LocatorDeniedError (terminal)
cloud frame.fetch
The scheme is neither http nor https.
Use http or https.
No
fetch: only http(s) urls are supported
LocatorDeniedError (terminal)
extension frame.fetch
The same rule as the row above, decided in the content script rather than on the page, so the text carries no frame. prefix.
Use http or https.
No
fetch: the url resolves outside the site this call names
LocatorDeniedError (terminal)
extension frame.fetch
The url read on its own and the url resolved against the landing document's base name different hosts, so the host the consent row would name is not the host the request would reach. //other.example/x and a page-set <base href> are the usual forms.
Pass an absolute url whose host is the one you mean.
No
frame.fetch: the target is outside the origins this attachment declared
LocatorDeniedError (terminal)
cloud frame.fetch
The hostname is not in domains and the origin is neither the attach target nor a goto target. The message deliberately does not echo where the call tried to go.
Declare the host in domains at attach, or goto it first.
No
frame.<operation>: the user did not grant <category> on <host>, ending in `here` when the frame names no host
LocatorDeniedError (terminal)
cloud frame.fetch, and every gated cloud locator call
The consent card was refused or dismissed. A dismissal starts a 10000 ms cooldown during which the card is not shown again and the call fails closed. A document-bound operation ends <category> here instead, naming no host, since the frame may have moved somewhere your app never named.
The message opens with the operation, so test it with message.includes(...) rather than startsWith. Ask again after the cooldown, or explain why the app needs it.
Yes, after the cooldown and a grant
frame.<operation>: the user has not granted <grant> here, and frame.fetch: the user has not granted network on <host>
LocatorDeniedError (terminal)
every gated operation on the cloud backend
The render proxy page re-ran the same policy and found no stored grant, without prompting. It checks every gated operation, not just frame.fetch. A document-bound operation ends in here and names the grant, which is the category id for an ordinary key and the key itself for a critical one; a fetch names network and the target host instead.
The message opens with the operation, so test it with message.includes(...) rather than startsWith. Ask again later, or explain why the app needs it.
Yes
frame.fetch: the call completed but its audit receipt could not be recorded; result withheld
LocatorDeniedError (terminal)
extension frame.fetch
The fetch ran but its activity-log receipt could not be written. The result is withheld rather than returned unrecorded, and the name is terminal so the loop does not re-issue a state-changing request.
Treat the request as having happened with an unknown result.
No
frame.fetch: <error>; not retried, since the request may already have been sent
LocatorDeniedError (terminal)
extension frame.fetch, from the first extension store release after 0.1.54
The request failed in the landing document after it may have left: a network error, a cross-origin read the browser refused, or a redirect hop onto an FKN platform host that the extension blocked. <error> is the browser's name: message, such as TypeError: Failed to fetch. The call ends there rather than being sent again every 50 ms to the deadline, so a POST goes out at most once. Through 0.1.54 the loop sent it again until the deadline and ended with a TimeoutError.
Treat the request as possibly sent. A cross-origin url has to answer the landing document with CORS, and its redirects have to stay off FKN hosts.
Sometimes, after a network error; never for a refused read or a blocked hop
fetch on the shared render proxy session, fetch on a frame holding no proxied document; navigate first, fetch is not available while the proxied document is on its own origin
LocatorUnsupportedError, backend 'render proxy'
cloud frame.fetch
The render proxy's own refusals. The third fires whenever the proxied document sits on its own frame-host origin, which is the deployed default, so cloud frame.fetch normally ends here.
Use the extension backend for frame fetches.
No
navigation pending; the target document has not committed yet
Error (retryable)
any cloud locator call made while a goto is still in flight, one not awaited first
The render proxy is navigating and the goto's document has not committed. Since @fkn/lib 0.9.42 both 'load' and 'commit' resolve after the commit, so an awaited goto never leaves a call here.
Let the dispatch loop retry; no handling is needed.
Yes, to the deadline
A timeout rarely says so in its message. The retry loop runs until its 30,000 ms deadline and then rejects with a TimeoutError carrying the last attempt’s message and that attempt as cause, so test the class, see what a timeout says. isTerminalError matches the three error names that stop the loop early, listed on locators and actions.
Every gated extension call can fail with the consent refusal. It is the error your call gets when a user denies a row on the consent sheet, the prompt the extension shows before an action above severity 0. A row is one category of access on one website, so a refusal covers every capability in that category there:
PermissionDeniedError, with grantKey, category, site, hosts, permissionKey and scope
every gated extension call: fetch with credentials: 'include', cookies.get, setRequestHeaderRule, every locator operation, frame.goto, frame.fetch, extension.attachFrame
The user refused, dismissed the consent sheet, or a stored deny covers that category on that site. The message names the row first and the concrete call in brackets, and a call that resolved no site drops the on <site> half. A dismissal records deny with remember: 'once', so a retry asks again. The gate raises it before the retry loop, so it is not retried.
Match error.name === 'PermissionDeniedError', or use the exported isPermissionDenied. isLocatorDenied and isTerminalError do not match this name, which is the single most common wrong assumption in this area. See Permissions and consent.
Yes if the user changes their mind; no while a session or always deny stands
permissions.request: pass hosts, the sites the grant applies to
TypeError
the category form of permissions.request
A category ask carried no hosts, or none of them survived normalisation. A grant row is keyed by hostname, so there is no row such an ask could be drawn on.
Pass hosts, the sites the grant applies to. The pre-category { key, scope } form needs none and is unaffected.
No
frame.requestPermissions: navigate the frame or declare domains first
TypeError
cloud frame.requestPermissions, and attachFrame({ permissions }) through it
The ask has no host to name: the frame holds no document and the attach declared no domains. A grant row is keyed by host, so there is no row such an ask could be drawn on.
Declare domains at attach, or goto a target before asking.
No
frame.request: navigate the frame or declare domains first
TypeError
extension frame.requestPermissions, and attachFrame({ permissions, cookies: 'native' }) through it
The row above, as the extension's content script words it: it names the method frame.request, its name before @fkn/lib 0.9.42, until an extension release renames it. A blank frame has no content script to answer a site lookup, so the declared set is the whole row there.
Declare domains at attach, or goto a target before asking.
No
permission rpc: the background answered with an unknown shape
Error
permissions.request and anything that consults the store
The background's reply did not match the expected envelope.
Retry; reload the extension if it persists.
Yes
Whatever response.error says
Error
permissions.request and anything that consults the store
The background answered a structured failure. Its store mints no sentence of its own, so the text is the message of whatever rejected under it, which is the extension's database layer in the browser; nothing in the library fixes it.
Every packages.* member, in the host app and in the package it loads, fails with a plain Error that carries one of six codes. @fkn/lib/packages types that shape as PackagesError:
Message
Name or code
Surfaced by
What happened
What to do
Retryable
packages: no FKN transport in this realm - use relayWorker to bridge workers
Error, code unavailable
every packages.* member except attach, onConnect, isVisible, onVisibilityChange
Neither window nor self is available.
Call relayWorker(worker) from the page. See Workers.
Yes, after bridging
packages.<call>: caller identity is not established
The broker could not attribute the call to an app identity.
Retry after the broker connection settles.
Yes
packages.<call>: <uri parse failure>
Error, code invalid
install, uninstall, connect, mount, show, hide
The uri failed to parse. Underlying sentences: A source uri must be a string of 1 to 512 characters, '<input>' has no '<handler>:' prefix, No handler for '<handler>:', and packages: '<handler>:' is not served by the npm registry.
Pass npm:<name>.
No
packages.install: version must be a valid npm version string
Error, code invalid
install
The pinned version fails the version pattern.
Fix the version.
No
packages.install: could not persist the install record (storage may be full or blocked)
Error, code unavailable
install
The broker could not write the install record.
Free space, or check that storage is not blocked for the origin.
Yes
packages.search: query must be an object, packages.search: type must be a short lowercase token, packages.search: id must be a short lowercase token, packages.search: unknown origin '<origin>'
Error, code invalid
search, pick
The query is malformed.
Fix the query.
No
packages.search: the npm registry did not answer
Error, code unavailable
search, pick
The registry request failed.
Retry.
Yes
packages: could not resolve '<name>' from the npm registry
Error, code unavailable
install, pick
The packument fetch failed or answered a non-object.
Retry.
Yes
packages: '<name>' has no latest version, packages: '<name>' has no version '<version>'
Error, code invalid
install, pick
The registry knows the package but not that version.
Pick a published version.
No
packages: another package prompt is already open
Error, code unavailable
pick, install
The broker's exclusive UI slot is taken.
Wait for the open prompt to close, then retry.
Yes
packages.<call>: '<uri>' is not installed by this app
Error, code not-installed
connect, mount
This app holds no install record for the package.
Call packages.install(uri) first.
Yes, after install
packages.show: '<id>' is not connected by this app - connect() before showing it
Error, code not-installed
show
The package is installed but not connected.
Call connect(uri) first.
Yes
packages.<call>: '<uri>' has been disabled by the platform
Error, code denied
connect, mount
The platform has switched the package off.
Remove the package from the app; nothing app-side can lift this.
No
packages.show: a package cannot place its own frame
Error, code denied
show called from inside a package tenant
Only a host app may place a frame.
Place the frame from the host app, never from inside the package.
No
packages.connect: '<uri>' was released while connecting
Error, code not-installed
connect
The package was uninstalled mid-connect.
Reinstall and retry.
Yes
packages.connect: '<uri>' failed to boot: <failure>
Error, code unavailable
connect
The tenant frame reported a boot failure.
Read <failure>; it is the package's own boot error.
Sometimes
packages.connect: '<uri>' did not register a connection handler
Error, code timeout
connect
The tenant never called onConnect.
Add a packages.onConnect handler in the package. See Packages.
Sometimes
packages.connect: the package did not complete the connection
Error, code timeout
connect, mount, attach
The connection handshake on the port did not settle within 30000 ms.
Retry.
Yes
packages.connect: the package refused the connection, or the package's own nack text
Error, code unavailable
connect, mount, attach
The package's createPayload threw, or it sent a nack. The package's Error.message is used when it supplied one.
Read the text; it is the package's.
Depends
packages.connect: aborted before connecting
Error, code unavailable
connect, mount, attach with a signal
The signal aborted before or during the handshake.
Treat it as the normal result of aborting.
Yes
packages.connect: the package closed before connecting
Error, code unavailable
connect, mount
The broker's closed promise settled during the handshake.
Retry.
Yes
packages.show: pass an element or a rect
Error, code invalid
show
Neither placement option was given.
Pass one.
No
packages.show: a rect with finite x, y, width and height is required
Error, code invalid
show
The rect has a non-finite member.
Fix the rect.
No
packages.mount: pass the iframe to load the package into
Error, code invalid
mount
options.iframe is not an HTMLIFrameElement.
Pass one.
No
packages.mount: the iframe must be in the document before mounting into it
Error, code invalid
mount
A detached frame never navigates, so src would resolve into a 30 s wait for a tenant that is not booting.
Append the iframe first.
Yes, after appending
packages.mount: the iframe's sandbox attribute must include <tokens>, or the package cannot start
Error, code invalid
mount
A sandbox attribute without allow-scripts and allow-same-origin. The tenant boots a service worker on its own origin and needs both.
Add the tokens or remove the attribute.
No
packages.mount: this page is cross-origin isolated, so the iframe's allow attribute must include 'cross-origin-isolated' to hand that down to the package
Error, code invalid
mount from a cross-origin isolated page
Isolation defaults to self, so a page that does not hand it down silently drops the package to no SharedArrayBuffer.
Add cross-origin-isolated to allow before mounting.
No
packages.mount: the package did not register a connection handler
Error, code timeout
mount
The tenant did not report ready within 30000 ms. The frame is unmounted before this throws.
Add a packages.onConnect handler in the package. See Packages.
Sometimes
packages.mount: the package failed to boot, or the tenant's own failure text
Error, code unavailable
mount
The tenant reported a boot failure.
Read the text; it is the package's.
Depends
packages.mount: the frame was detached before it could connect
Error, code unavailable
mount
contentWindow went away between the ready message and the port handoff.
Retry with a stable iframe.
Yes
Branch on code. A worker that was never relayed has to be relayed from the page first. The codes are on packages and the worker case is on workers.
Every member of @fkn/lib/rooms, and every method on a Room, fails with a plain Error that carries one of twelve codes, typed as RoomsError:
Message
Name or code
Surfaced by
What happened
What to do
Retryable
rooms: wrong room key
Error, code bad-key
rooms.open, rooms.join
The room exists, because somebody holds a seat or a claim keeps it, and the key presented is not the one it was opened with. rooms.open without key mints a fresh one, so it meets this whenever the name is in use.
Pass room.invite whole to rooms.join, or the room's own key to rooms.open.
No
rooms: the invite carries no key
Error, code invalid
rooms.join
The invite has no dot, or nothing on one side of it, so @fkn/lib refuses it before the broker is asked. The broker answers the same for a key that is not 32 bytes of base64url or an id that is not a room id.
Pass room.invite as it was, the key and the id joined by a dot.
No
rooms: the name is not one a room can have
Error, code invalid
rooms.open, rooms.create
The name is empty, over 72 characters, or outside the grammar once capitals fold to lowercase: 1 to 64 of a to z, 0 to 9, ., _ and -, a letter or digit at each end, and never fkn or a name starting fkn-. The broker answers the same when the calling app has no scope a room can live under.
Fold the name to lowercase and keep it to that alphabet, or let rooms.create pick one.
No
rooms: the key is not 32 bytes base64url
Error, code invalid
rooms.open, rooms.create
The key option does not decode to exactly 32 bytes, or is not written as unpadded base64url.
Pass 32 bytes as unpadded base64url, 43 characters, or leave key out so the broker mints one.
The id names nobody this room holds, or, for unblock, nobody it has blocked.
Take ids from room.members() and from the room events, and drop one on a left event.
No
rooms: no such message
Error, code not-found
room.edit, room.delete
The mailbox holds no message under that seq, or nothing in the range, because it was deleted or never stored.
Take seq from a message event or a backlog page, and drop it on a deleted event.
No
rooms: the room is full
Error, code full
rooms.open, rooms.join
The room already seats its member cap, counting members inside the hold.
Wait for a member to leave. The cap is set once, by the members option of the join that brought the room into being, and is at most 100.
Yes
rooms: too many connections
Error, code full
rooms.open, rooms.join
This member already holds the connection cap in this room, across its tabs and devices.
Leave the room in a tab that no longer needs it, then open it again.
Yes
rooms: too many blocks
Error, code full
room.block
The room's block set is full, and it refuses rather than dropping an entry.
Unblock somebody first, since dropping an entry would let them back in.
Yes, after an unblock
rooms: too many claims
Error, code full
room.claim
The account already holds its claim cap: 10 claims, or 2 under global for a name there. The claim waited on the room limit card fkn.app showed over your app, and the person closed it without freeing a slot, or fkn.app could not show it.
The person can unclaim a room on that card or with Unclaim in the Rooms tab of its fkn.app settings, and the app that claimed a room can release it. Claim again when they ask, not in a loop, since each claim at the limit shows the card again.
Yes, after a release or an Unclaim
rooms: you are blocked from this room
Error, code blocked
rooms.open, rooms.join
A member holding block blocked this member, by its id and by its account, or for a guest by its network, and a block lives as long as the room.
Nothing app-side lifts it. A member holding block can call room.unblock(id).
No
rooms: you cannot send here
Error, code denied
room.send
This member's send is false, from the room default or from a revoke.
Read room.self.permissions.send and hide the composer while it is false.
The caller lacks what the call needs: remove or block for those calls, receive for backlog, and for edit and delete a message of its own, one at a time for delete, a rule only the owner is exempt from.
Offer each control only while room.self.permissions carries what it uses, and edit or delete only your own messages unless you own the room.
Compare room.self.id with room.owner before offering these calls.
No
rooms: the owner cannot be removed
Error, code denied
room.remove, room.block
The target is room.owner, and nobody removes or blocks the owner.
Render no remove or block control against the owner.
No
rooms: the owner keeps every permission
Error, code denied
room.grant, room.revoke, room.limit
The target is room.owner, whose four permissions and message cap are constant.
Skip the owner when you render per-member permission controls.
No
rooms: you cannot remove yourself
Error, code denied
room.remove, room.block
The target id is room.self.id.
Call room.leave(), which is the call that means leaving.
No
rooms: claiming needs an account
Error, code denied
room.claim
This connection was opened with no FKN account connected, or the account no longer exists.
Connect the account before opening a room you mean to claim. A connection keeps the sign-in it opened with, and connecting can give the person a new member id, which is then not the owner.
Yes, in a room opened after connecting
rooms: claiming needs premium
Error, code denied
room.claim, room.setMailbox(true)
The connected account is not premium, as the room read it when this connection opened or as the service answers now. Turning a claimed room's mailbox on needs premium as a claim does, even when it is on already, and turning it off never does.
Offer the claim only to a premium account, and after an upgrade open the room again so the connection carries it.
Yes, after an upgrade and a reopen
rooms: the name is claimed
Error, code denied
room.claim
Another account holds the claim on this name, or claimed it while this request was on its way.
Pick another name. A claim stays with its account until it is released or lapses, or the account unclaims it in fkn.app.
No
rooms: the description is not one a claim can carry
Error, code invalid
room.claim
The description is not a string, or once trimmed it is empty, over 200 characters as String.length counts them, or holds a control character, a line or paragraph separator, a bidi control or a lone surrogate. @fkn/lib refuses a value that is not a string before the broker is asked, and the rooms service refuses the rest, so nothing about the claim changes.
Pass one line of plain text of 1 to 200 characters, shortening it yourself, or claim without a description, which keeps the stored one. An empty one is refused too, so a description cannot be removed: it goes only with the claim.
No
rooms: only the app that claimed the room can describe it
Error, code denied
room.claim
The account already holds the claim of a room under an app's scope, any scope but global, made from another of its apps, as when this app reached the room by invite. There a claim with a description replaces the stored one only from the app that claimed the room, the same app once it is verified, so the account never reads one app's words under another app's name. In a global/ room any app of the account describes it.
Claim without a description, which still resolves for the account, or describe the room from the app that claimed it.
No
rooms: the temporary duration is not one a claim can carry
Error, code invalid
room.claim
The temporary option is neither a boolean nor a whole number of milliseconds from 60,000 to 31,536,000,000, 1 minute to 365 days. @fkn/lib refuses it before the broker is asked, and the rooms service checks the range again, so nothing about the claim changes.
Pass true for 7 days, or a whole number of milliseconds inside the range. ROOM_TEMPORARY_MS from @fkn/lib/contract carries the default and both bounds.
No
rooms: only the app that claimed the room can set when it expires
Error, code denied
room.claim
The account already holds the claim of a room under an app's scope, any scope but global, made from another of its apps, and this claim passed temporary. There only the app that claimed the room, the same app once it is verified, sets or changes when its claim ends. In a global/ room any app of the account does.
Claim without temporary, which still resolves for the account and leaves the room as it was, or set the duration from the app that claimed it.
No
rooms: temporary claims are not available
Error, code unavailable
room.claim
The claim passed temporary, and the broker holding the room is older than that option, so it would drop it and make the claim permanent. @fkn/lib refuses rather than keep a room longer than the app asked for.
Nothing app-side makes an old broker carry it: a claim without temporary still works there, and the person gets a current broker the next time fkn.app loads.
Yes, once fkn.app has updated
rooms: the mailbox switch is not available
Error, code unavailable
room.claim with mailbox: false, room.setMailbox
The broker holding the room is older than the mailbox switch, so it would drop mailbox: false and make a claim that keeps messages, and it has no setMailbox. @fkn/lib refuses rather than store what the app asked it not to, with nothing sent.
Nothing app-side makes an old broker carry it: a claim that keeps a mailbox still works there, and the person gets a current broker the next time fkn.app loads.
Yes, once fkn.app has updated
rooms: only the app that claimed the room can release it
Error, code denied
room.release
The account holds the claim of a room under an app's scope, any scope but global, made from another of its apps. Only the app that claimed such a room releases it, the same app once it is verified, so the claim and its mailbox stand. Any app of the account releases a global/ room.
Release it from the app that claimed it, or unclaim it from the Rooms tab in fkn.app.
No
rooms: only the app that claimed the room can change it
This room is under an app's scope, any scope but global, and the account claimed it from another of its apps: only the app that claimed it, the same app once it is verified, changes it. It holds even where room.self.id equals room.owner, as it does for an account whose encryption key the browser holds, and for the account's own messages, since a member id belongs to the account and not to the app. Nothing in the room changes.
Make the change from the app that claimed the room, or from the Rooms tab in fkn.app for what it offers. This app still joins, reads and sends there, and a global/ room takes these changes from any app of the account.
No
rooms: the room is not claimed
Error, code invalid
room.release, room.setMailbox
The room holds no claim to release, or whose mailbox to turn off or on.
Read room.claimed, or wait for the claim event, before offering a release or the mailbox switch.
No
rooms: the room keeps no messages
Error, code invalid
room.edit, room.delete
The room has no mailbox, so it stored nothing it relayed: nobody has claimed it, or its claim keeps no messages, made with mailbox: false or turned off since with room.setMailbox(false) or the Messages kept switch in the owner's fkn.app settings. While it keeps none the room refuses a size limit from those settings the same way, since a mailbox turned on later starts with no limit, and fkn.app says so on the room's row: no app call receives that one.
Offer edit and delete only while room.mailbox is not null, and drop them on a claim event whose mailbox is false.
No
rooms: the limit is out of range
Error, code invalid
room.limit
maxMessageBytes is not a whole number from 1 to 33,554,432.
Clamp the value to that range, or pass null with a member id to clear that member's override.
No
rooms: sending too fast
Error, code rate-limited
room.send
The per-member or per-room rate is spent. Ten such refusals inside ten seconds close the connection, and the broker dials it again.
Queue the text and send it again a moment later, and never in a tight loop.
Yes
rooms: the message is too large
Error, code too-large
room.send, room.edit
The sealed message, nonce and ciphertext in base64url, is over this member's maxMessageBytes: 262,144 by default, and never over the platform ceiling. Nothing is trimmed.
Read room.self.maxMessageBytes and split the text to fit. Four thirds of its UTF-8 byte length, plus 39, never undercounts the sealed size.
Yes, with shorter text
rooms: the mailbox is full
Error, code storage
room.send, and room.edit when the text grows
The claimed room's mailbox would pass 500,000,000 bytes, or the lower size limit the account set for the room in its fkn.app settings, and it refuses rather than dropping old messages.
Delete old messages with room.delete as the owner, then send again, and read cap from room.usage() for the limit that applies. A room that needs no history can stop keeping messages instead, with room.setMailbox(false), which deletes what it kept.
Yes, after a delete or a higher limit
rooms: the owner is out of storage
Error, code storage
room.send, and room.edit when the text grows
The claiming account is over its storage quota, files, mailboxes and stored objects together, as the service last answered.
Free storage in the owner's account, by deleting files or by clearing, unclaiming or turning Messages kept off for a room in the Rooms tab of its fkn.app settings, then send again a minute later: a refused write makes the room ask the service again within a minute.
Yes, once the owner has room
rooms: out of daily bandwidth
Error, code quota
room.send, room.edit, room.backlog
This free account, or this guest's network, has spent the day's free volume, which room traffic in both directions shares with cloud egress. A premium account is not metered.
Wait for the next UTC day, or open the room again signed in on a premium account. Messages keep arriving meanwhile.
Yes, the next UTC day
rooms: rooms are unavailable
Error, code unavailable
rooms.open, rooms.create, rooms.join, and every member of a joined Room
There is no broker to ask, the broker was replaced while the call was pending, the connection could not be opened or was lost, the platform refused this connection's sign-in, the service behind a claim did not answer, or a claimed room under an app's scope could not find out whether this app is the one that claimed it.
Check rooms.available() first, and open the room again from the invite.
Yes
rooms: malformed frame
Error, code invalid
rooms.open, rooms.create, rooms.join, and every member of a joined Room
The platform could not read a frame, or a value in it is outside what the frame allows: limit(null) with no member id, a delete range that runs backwards, a backlog limit under 1, a defaults.maxMessageBytes over the platform ceiling, or a mailbox option on claim or an argument to setMailbox that is not a boolean, which @fkn/lib refuses before the broker is asked. The broker answers the same to a claim or release from a caller it cannot name as an app, and the rooms service to a claim or release naming another app than the one the connection joined as, which @fkn/lib and the broker never send.
Pass null to limit only with a member id, and keep ranges, counts and sizes positive and in order.
No
rooms: the room has ended
Error, code closed
every member of a joined Room, once it has ended for this app
The room is over here: you left, in this tab or another, you were removed or blocked, or the rejoin window passed.
Read await room.closed for the reason, and call rooms.join(invite) for a fresh Room.
No, this Room is finished
Branch on code, never on the text: the wordings are the library’s own literals, kept here so the catalogue can find them, and a code survives a rewording. Which code each method raises is on rooms.
Every member of @fkn/lib/storage, and every read of the blob storage.get resolves, fails with a plain Error that carries one of eight codes, typed as StorageError. An aborted signal rejects with the signal’s own reason instead:
Message
Name or code
Surfaced by
What happened
What to do
Retryable
storage: the data is not a Blob or a ReadableStream
Error, code invalid
storage.put
The data is neither a Blob, a File included, nor a ReadableStream, or a stream delivered a chunk that is not a Uint8Array. A wrong type is refused before the broker is asked.
Pass the File or Blob itself, or a ReadableStream<Uint8Array> with its size.
No
storage: a stream needs the size it will deliver, a whole number of bytes
Error, code invalid
storage.put
A ReadableStream came without size, size is not a whole number of bytes from 0 up, or a Blob came with a size other than its own.
Pass size as exactly the bytes the stream delivers, and leave it out for a Blob.
No
storage: the stream did not deliver the size it declared
Error, code invalid
storage.put
The stream ended before size bytes, or carried on past them. The broker aborts the upload, and the storage it reserved is released.
Declare the exact length of what the stream delivers, or pass a Blob.
No
storage: the file is too large to store
Error, code invalid
storage.put
The file is larger than 167,772,160,000 bytes, 10,000 parts of 16 MiB, the largest object the service lays out. Nothing was uploaded.
Split the file across several objects and keep their urls together in your app.
No
storage: the key is not 32 bytes base64url
Error, code invalid
storage.put, storage.get
The key is not a string, or it does not decode to exactly 32 bytes written as canonical unpadded base64url. Nothing was sent.
Pass the key that put answered, unchanged, or 32 random bytes as unpadded base64url, 43 characters. Leave it out of put to have one minted.
No
storage: the url does not name a stored object
Error, code invalid
storage.get, storage.delete
The url is not a string, or it is not exactly the service's address followed by a lowercase UUID: another host, an uppercase id, a trailing slash, a query or a path below the id are refused before any request.
Pass the url that put or list answered, unchanged.
No
storage: the limit is out of range
Error, code invalid
storage.list
The limit is not a whole number from 1 to 1,000.
Pass 1 to 1,000, or leave limit out for pages of 1,000.
No
storage: the cursor is not one list answered
Error, code invalid
storage.list
The cursor is not in the shape a page of list answers.
Pass the previous page's cursor unchanged, and none for the first page.
No
storage: no object at this url
Error, code not-found
storage.get, and every read of the blob it resolves: stream, arrayBuffer and a slice's; storage.put when the upload is deleted while it runs
No object is ready at the url: it was never made, it was deleted, or it is still uploading, one answer for all three. A read that starts after a delete meets it, while a read already streaming when the delete landed finishes.
Treat the file as gone, and ask whoever shared the url for a new one.
Yes for an object still uploading, once it is ready, and no otherwise
storage: the object does not match its key
Error, code integrity
the first read of the blob storage.get resolves: stream, arrayBuffer and a slice's; storage.get itself when the stored size is not one the format produces
A 1 MiB record did not open under the key: the key is not the one the object was sealed with, or the bytes are not this object's. get resolves before any record is read, so a wrong key shows on the first read.
Read with the key that put answered for this url. Asking again with the same key gives the same answer.
No
storage: storing needs an account
Error, code denied
storage.put, storage.list, storage.delete
No FKN account is connected to this app. Only get works without one.
Call connect() first, and check storage.available() before offering an upload.
Yes, once an account is connected
storage: only the app that stored the object can delete it
Error, code denied
storage.delete
The object belongs to this account, and another app stored it. The app that stored it is the only one that deletes it, the same app once it is verified.
Delete it from the app that stored it. To share deletion, have that app do it on request.
No
storage: storage quota exceeded
Error, code quota
storage.put
The account's storage quota, its files, claimed room mailboxes and stored objects together, has no room for the sealed size, either when the upload starts or when it completes.
Delete objects or files the app no longer needs, or tell the person their storage is full.
Yes, once space is freed
storage: too many stored objects
Error, code too-many
storage.put
The account already holds 10,000 objects, uploads in progress included, counted apart from its cloud.fs files.
Delete objects the app no longer needs before storing more.
Yes, after a delete
storage: too many objects stored this hour, try again later
Error, code too-many
storage.put
The account has started 1,000 uploads this hour on a free plan, or 5,000 on premium. The hour opens with the first upload it counts.
Queue the upload and send it once the hour is over, rather than retrying in a loop.
Yes, once the hour is over
storage: the signed-in account changed
Error, code account-changed
storage.put, storage.list, storage.delete
The account this page was on when the call was made is no longer the one signed in, so nothing was stored, listed or deleted for the new one.
Read the account again, and repeat the call for the new account only if the person asks for it.
No, a new call acts for the new account
storage: storage is unavailable
Error, code unavailable
every member of @fkn/lib/storage, and every read of a blob storage.get resolved
No broker serves storage here, as in Node or on a broker older than storage, or the broker, the service or the store did not answer after its retries, or a refusal arrived whose code this library does not know.
Check storage.available() before an upload, and try the call again later.
Yes
Branch on code, never on the text. These messages share the storage: prefix with the file system rows above, so a startsWith('storage: no object at') matches a missing file and a missing object alike, and storage: the signed-in account changed is also how the cloud.fs account change error, storage: the signed-in account changed, so this call was not made, begins. Which call raises which code is on object storage.
The bounded wait for a broker, a broker replaced while a call was pending, the two rejections of an awaited relayWorker call, and a call into the extension that its stopped worker cut off all land here. The last two rows come with the next FKN extension store release, the first after 0.1.54:
Message
Name or code
Surfaced by
What happened
What to do
Retryable
@fkn/lib: no broker connection within <ms>ms, so <what> could not be requested
BrokerUnreachableError
net.Socket#connect, net.Server#listen, dgram.Socket#bind, and anything else built on apiWithin from @fkn/lib/api
No broker connection settled within the deadline: 8000 ms, and once any call has missed it, every later call waits only 1000 ms.
Use apiWithin(what) wherever the caller owns a socket, a timer or a UI: it is the bounded alternative to apiPromise, which never rejects and parks forever in a realm with no broker. See Connection and lifecycle.
Yes
FKN: the broker was replaced while this call was pending; retry it
Error
any call in flight when the broker document is replaced (its update flow reloads the broker frame)
A call is bound to the broker connection it started on. When that document is replaced, pending calls are rejected rather than left to hang silently; the new connection is already routed.
Match message.startsWith('FKN: the broker was replaced') and retry once.
Yes, and the message says so
FKN @fkn/lib: relayWorker must be called from the main thread
Error
relayWorker
No window.
Call it from the page.
No
FKN @fkn/lib: relayWorker found no FKN transport in this realm
Error
relayWorker
This realm has neither a mounted broker frame nor a MessagePort granted by a parent FKN realm.
Import the library on a page that mounts the broker frame before calling relayWorker.
Yes, once the transport is up
the FKN extension's background stopped before it answered
BackgroundStoppedError
a call into the extension while Chrome stops its worker: extension.fetch and the root fetch on the extension path, cookies.get, the two header rule calls, and attachFrame and goto on cookies: 'native', from the next FKN extension store release on
Chrome stops an extension's service worker once it has been idle for a while, and this one stopped before the call resolved. The call may or may not have run, so the extension never sends it again, and the next call is answered by the restarted worker. Raised from the first store release after 0.1.54; through 0.1.54 a call made after such a stop stays pending until the page reloads.
Match it with isBackgroundStopped(), then repeat the call yourself only when running it twice is harmless, such as a GET. See a call the extension never finished.
Yes, as a new call of yours, never resent by the extension
the FKN extension's background stopped while the response body was still arriving
BackgroundStoppedError
reading the body of an extension.fetch response, or of the root fetch on the extension path, from the next FKN extension store release on
The response had resolved and its body was still streaming from the extension when Chrome stopped its worker, so the read rejects rather than ending early. The request is never sent again. Raised from the first store release after 0.1.54, as the row above.
Discard what was read, and repeat the fetch only when running it twice is harmless.
Yes, as a new call of yours, never resent by the extension
@fkn/lib and the extension declare nine errors that no routed call reaches. They are listed here so that you do not write a handler for them:
Message
Name or code
Why it cannot fire
owner: this realm has no bridge to its parent frame
LocatorUnsupportedError, operation 'owner'
Since @fkn/lib 0.9.42 owner() is Playwright's: on a FrameLocator it is the Locator on the iframe it entered, built in the page from parts every backend resolves, and the Frame has no owner(). Nothing in the library sends the upward hop that raises this.
owner: already at the top frame
LocatorUnsupportedError, operation 'owner'
Since @fkn/lib 0.9.42 owner() is Playwright's: on a FrameLocator it is the Locator on the iframe it entered, built in the page from parts every backend resolves, and the Frame has no owner(). Nothing in the library sends the upward hop that raises this.
owner: the parent frame is outside this bridge boundary
LocatorUnsupportedError, operation 'owner'
Since @fkn/lib 0.9.42 owner() is Playwright's: on a FrameLocator it is the Locator on the iframe it entered, built in the page from parts every backend resolves, and the Frame has no owner(). Nothing in the library sends the upward hop that raises this.
Locator operation not supported on the cloud backend: <operation>
LocatorUnsupportedError
The cloud backend's set of unsupported operations is empty, so the guard before every call never matches, and the render proxy registers videoElement as well, so nothing refuses it either.
rooms: rooms are not available
Error, code unavailable
The library carries the wording and never throws it, so a broker with no rooms service to dial answers rooms: rooms are unavailable instead.
rooms: already joined
Error, code invalid
The broker sends exactly one join per connection, the first frame after it opens, and every open, create and join opens a connection of its own, so no call reaches the second join this refuses.
rooms: not joined
Error, code invalid
The broker sends a join first on every connection, and every path that takes a member's seat away closes that member's connections in the same step. A closed connection carries no reply, so a request caught there settles rooms: rooms are unavailable or rooms: the room has ended instead.
The FKN desktop app is not connected, desktop.<name> is unavailable
Error
No automatic routing reaches the desktop backend: desktop.available() is hardcoded false, so the root fetch never falls to it, and only a direct desktop.* call sees the throw.
attachFrame: this FKN WebExtension does not serve the window option "<key>", so it refuses the attach rather than ignore it. Updating the extension may add it.
TypeError
@fkn/lib 0.9.47 sends a window's url and domains alone, and every extension that serves a window reads both. Only a later library sending a new window option to an older extension can meet it.
The three owner: rows belong to the upward hop the raw locator wire still carries: since @fkn/lib 0.9.42 owner() is Playwright’s, a Locator on the iframe a frameLocator() entered, and nothing in the library sends that hop. Only a direct desktop.* call sees the desktop row, as backends explains. The window option row guards an extension against a later library: @fkn/lib 0.9.47 sends only the options every extension that serves a window reads.