Small fixes
A selection of 80 fixes from 174 pull requests.
1.BigInt precision
The autofix converted 9007199254740993n through a Number, rounding it to 9007199254740992n. Quoting the digits preserves the original value.
11const fixedSource = `BigInt(${node.value})`const fixedSource = `BigInt("${node.value}")`2.The wrong database
A query through client B could borrow client A’s active transaction and hit database A. Each client now has its own transaction-context key.
123const LibsqlTransaction = Context.Service<readonly [LibsqlConnection, counter: number]>( `@effect/sql-libsql/LibsqlClient/LibsqlTransaction/${clientIdCounter++}`)3.Lost leftovers
Take one item, then zero, then collect: [1, 2, 3] followed by [4, 5] left only [4, 5]. The continuation must retain the unread 2 and 3.
1123456)).pipe(Effect.map((end): End<A1, L | L1> => { if (!leftover) { return end } return [end[0], end[1] ? [...end[1], ...leftover] : leftover]}))4.Binary data, decoded twice
Merely selecting the text reader made a later binary read return decoded, re-encoded UTF-8. Text must derive from cached bytes, not replace them.
1234567891011121this.textBody = Effect.tryPromise({ try: () => this.source.body.text(), catch: (cause) => new Error.HttpClientError({ reason: new Error.DecodeError({ request: this.request, response: this, cause }) })}).pipe(Effect.cached, Effect.runSync)this.arrayBufferBody = Effect.map(this.textBody, (_) => new TextEncoder().encode(_).buffer)this.textBody = Effect.map(this.arrayBuffer, (_) => new TextDecoder().decode(_))5.Literal configuration values
String.replace interpreted $& in an environment value as a replacement instruction. A callback inserts the value literally, avoiding incorrect expansion and recursion.
11envValue.replace(group, value),envValue.replace(group, () => value),6.Partial writes
A successful file write may accept only some bytes. The logger ignored that count and lost the remainder. writeAll completes the batch.
11flush: (output) => effect.ignore(logFile.write(encoder.encode(output.join("\n") + "\n")))flush: (output) => effect.ignore(logFile.writeAll(encoder.encode(output.join("\n") + "\n")))7.Cleanup after a throw
The use callback ran before its cleanup handler was installed. If it threw while constructing an Effect, release was skipped. Suspending the callback fixes the order.
11restore(use(a)),suspend(() => restore(use(a))),8.Deleting a prefix
Removing “abc” also erased the value stored at “ab”. A trie node is empty only when it has neither children nor a value.
11234const nc = child.left === undefined && child.mid === undefined && child.right === undefined ? undefined : childconst nc = child.value === undefined && child.left === undefined && child.mid === undefined && child.right === undefined ? undefined : child9.Pipeline input
Writing to a pipeline entered its last process, bypassing earlier stages. Input belongs to the first process; output belongs to the last.
11stdin: handle.stdin,stdin: handles[0].stdin,11.Request zero
A truthiness check discarded valid request IDs 0 and "". Acknowledgements and interrupts must check for absence, not falsiness.
11return requestId ?return requestId !== undefined ?12.A zero cache lifetime
timeToLive: 0 was treated as omitted, allowing the registry’s nonzero default to retain old results. Only undefined should mean “use the default”.
11timeToLive: options?.timeToLivetimeToLive: options?.timeToLive !== undefined13.An exhausted comparison
The first comparison consumed an iterator of ordering rules. The second saw no rules and returned equality. Materialize the finite rules once.
12334const orders = Array.from(collection)return make((a1, a2) => { let out: Ordering = 0 for (const O of collection) { for (const O of orders) {14.Iterables are not arrays
A handler could return a generator, but the resolver looped over .length and indexed its results. No requests completed. for…of honors the declared Iterable contract.
112345678991011101112Effect.matchCause((fns[tag] as any)(requests) as Effect.Effect<Array<any>, unknown, unknown>, {Effect.matchCause((fns[tag] as any)(requests) as Effect.Effect<Iterable<any>, unknown, unknown>, { onFailure: (cause) => { for (let i = 0; i < requests.length; i++) { const entry = requests[i] entry.completeUnsafe(Exit.failCause(cause) as any) } }, onSuccess: (res) => { let i = 0 for (let i = 0; i < res.length; i++) { const entry = requests[i] entry.completeUnsafe(exitSucceed(res[i]) as any) for (const result of res) { const entry = requests[i++] entry.completeUnsafe(exitSucceed(result) as any)15.An error wrapped as an error
A handler’s “missing” error reached callers as a Cause object. Completing with failCause preserves the original failure instead of wrapping its description as a new error.
11entry.completeUnsafe(exitFail(cause) as any)entry.completeUnsafe(Exit.failCause(cause) as any)16.Day 5 became day 15
Type 1, Tab, 5 in an MM-DD prompt: the month’s digit carried into the day. Tab must clear the input buffer, as Left and Right already do.
11state: { ...state, cursor }state: { ...state, typed: "", cursor }17.Negative flag values
The wizard turned -2 into a separate option token. Generating --offset=-2 instead of --offset -2 delivers the value to the handler.
112345return values.flatMap((value) => [commandLineArg(`--${single.name}`), value])return values.flatMap((value) => value.value.startsWith("-") && value.value.length > 1 ? [commandLineArg(`--${single.name}=${value.value}`, `--${single.name}=${value.displayValue}`)] : [commandLineArg(`--${single.name}`), value])18.A suppressed React update
Switch refs from 0 to 1, then set the new ref to 0: React ignored the notification because its stored state was already 0. Count notifications instead.
1212const [, setValue] = React.useState(ref.value)React.useEffect(() => ref.subscribe(setValue), [ref])const [, forceUpdate] = React.useReducer((n) => n + 1, 0)React.useEffect(() => ref.subscribe(forceUpdate), [ref])19.Writing to the wrapper
A fallback atom reused its primary’s writer with the wrong context. setSelf updated the wrapper. Forwarding through the registry gives the primary its own context.
1123self.write,function(ctx, value) { ctx.set(self, value)},20.A stale tool schema
Replacing a tool’s parameters left its original JSON Schema override intact. It still advertised “query” while expecting “count”. Clear the override on replacement.
11return clone(this, { parametersSchema })return clone(this, { parametersSchema, jsonSchema: undefined })21.A bodyless response
A streaming HEAD response handed cleanup to its body stream, then omitted the body. Keeping HEAD on the normal cleanup path closes the request scope.
11response = scopeTransferToStream(response)if (request.method !== "HEAD") response = scopeTransferToStream(response)22.A raw streaming body
Node requires duplex: "half" for a ReadableStream request body. The adapter checked the wrapper’s tag, missing raw streams. Check the outgoing body itself.
11duplex: request.body._tag === "Stream" ? "half" : undefined,duplex: typeof ReadableStream !== "undefined" && body instanceof ReadableStream ? "half" : undefined,23.A missing URL prefix
Combining base /v1 with endpoint /items produced /items. URL resolution replaced the base path. Use the same prefix joining as the generated client.
1231234567891011121314const query = queryInput === undefined ? "" : UrlParams.toString(UrlParams.fromInput(queryInput))const url = query === "" ? path : `${path}?${query}`return options?.baseUrl === undefined ? url : new URL(url, options.baseUrl.toString()).toString()const urlParams = queryInput === undefined ? UrlParams.empty : UrlParams.fromInput(queryInput)if (options?.baseUrl === undefined) { const query = UrlParams.toString(urlParams) return query === "" ? path : `${path}?${query}`}const url = new URL( HttpClientRequest.prependUrl(HttpClientRequest.get(path), options.baseUrl.toString()).url)for (const [key, value] of urlParams.params) { if (value !== undefined) { url.searchParams.append(key, value) }}return url.toString()24.Unparsed form data
The client passed form-urlencoded text to a schema expecting UrlParams. Parse the text before decoding the record.
11234567return StringFromArrayBuffer.pipe(Schema.decodeTo(Schema.RecordFromUrlParams))return StringFromArrayBuffer.pipe(Schema.decodeTo( Schema.RecordFromUrlParams, SchemaTransformation.transform({ decode: (text) => UrlParams.fromInput(new URLSearchParams(text)), encode: UrlParams.toString })))25.A missing content length
Converting a Web Response to a stream erased its valid Content-Length. Carry the parsed length into the stream’s metadata.
112contentType ?? undefinedcontentType ?? undefined,bodyInternal.parseContentLength(response.headers.get("content-length"))26.Overwriting Vary
CORS replaced Vary: Accept-Language with Vary: Origin, erasing a cache constraint. Merge the dimensions; preserve Vary: * unchanged.
112344567897export const varyAcceptEncoding = (headers: Headers.Headers): string | undefined => {export const varyWith = (headers: Headers.Headers, dimension: string): string => { const vary = headers["vary"] if (vary === undefined) { return "Accept-Encoding" return dimension } const members = vary.split(",").map((member) => member.trim().toLowerCase()) return members.includes("*") || members.includes("accept-encoding") ? undefined : `${vary}, Accept-Encoding` return members.includes("*") || members.includes(dimension.toLowerCase()) ? vary : `${vary}, ${dimension}`27.The string “null”
sql.json("null") returned null, and sql.json("hello") failed to parse. PGlite treated strings as JSON source. Encode string values before binding.
12345345676const value = withoutTransform || transformValue === undefined ? type.paramA : transformValue(type.paramA)return [ placeholder(undefined), [ withoutTransform || transformValue === undefined ? type.paramA : transformValue(type.paramA) ] [typeof value === "string" ? JSON.stringify(value) : value]28.An uncaught SQL error
Uncached statement preparation ran outside Effect’s error capture. Its errors became defects rather than SqlError. Use the same preparation helper as the cached path.
11) => runStatementValuesUnprepared(db.prepare(sql), params)) => Effect.flatMap(prepare(sql), (statement) => runStatementValuesUnprepared(statement, params))29.The wrong column names
Later SQL statements reused the first statement’s column names, mislabeling rows and losing values. Return matching column names for each row.
11234556789let columns: Array<string> | undefinedconst columns: Array<Array<string>> = []for (const stmt of sqlite3.statements(db, sql)) { let statementColumns: Array<string> | undefined sqlite3.bind_collection(stmt, params as any) while (sqlite3.step(stmt) === WaSqlite.SQLITE_ROW) { columns = columns ?? sqlite3.column_names(stmt) statementColumns = statementColumns ?? sqlite3.column_names(stmt) const row = sqlite3.row(stmt) results.push(row) columns.push(statementColumns)30.An index is not a primary key
On an IndexedDB secondary-index cursor, key is the index value. Reconstructing a row’s identity requires primaryKey.
11? { ...cursor.value, key: cursor.key }? { ...cursor.value, key: cursor.primaryKey }31.Characters are not bytes
“€” counted as one unit against a byte watermark, although its UTF-8 encoding occupies three bytes. Measure the encoded payload.
1123if (highWaterMark !== undefined) {bufferSize += typeof data === "string" ? data.length : data.byteLength bufferSize += typeof data === "string" ? encoder.encode(data).byteLength : data.byteLength}32.Mixed line endings
An SSE event containing both CRLF and LF could disappear. The line scan must start at the current buffer position plus the retained partial-line offset.
11for (let index = startingPosition; lineLength < 0 && index < length; ++index) {for (let index = position + startingPosition; lineLength < 0 && index < length; ++index) {33.A missing first character
The binary codec consumed a leading U+FEFF as a byte-order marker. ignoreBOM: true preserves it as part of the original string.
11const utf8DecodeFatal = new TextDecoder("utf-8", { fatal: true })const utf8DecodeFatal = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true })34.A percent sign in a reference
The definition Rate% exported as #/$defs/Rate%, an invalid URI escape. JSON Pointer escaping must be followed by URI-fragment escaping.
1234function formatReferenceToken(token: string): string { return encodeURI(escapeToken(token)).replace(/#/g, "%23")}
35.Too many arguments
Buffering 200,000 elements with push(...arr) exceeded the engine’s call-argument limit. Append them individually instead.
1123for (let i = 0; i < arr.length; i++) {chunk.push(...arr) chunk.push(arr[i])}36.The excluded endpoint
A draw below 1 can still round to 20 in a [10, 20) range. If that happens, return the upper bound’s nearest representable predecessor.
123456789101112131415161718
/** @internal */export const nextBetween = (min: number, max: number, draw: number): number => { const value = draw * (max - min) + min if (value !== max || min >= max || !Number.isFinite(max)) { return value } // Rounding can reach the excluded endpoint even for a draw below 1. // Return its immediate predecessor, which is at least min for finite min < max. if (max === 0) { return -Number.MIN_VALUE } const view = new DataView(new ArrayBuffer(8)) view.setFloat64(0, max) const bits = view.getBigUint64(0) view.setBigUint64(0, max > 0 ? bits - BigInt(1) : bits + BigInt(1)) return view.getFloat64(0)}37.Cancelling the existing task
Re-registering the same fiber with onlyIfMissing interrupted it. Check identity before rejecting a new candidate.
1212434if (options?.onlyIfMissing === true) { fiber.interruptUnsafe(internalFiberId)if (self.state.fiber === fiber) { return} else if (self.state.fiber === fiber) {} else if (options?.onlyIfMissing === true) { fiber.interruptUnsafe(internalFiberId)38.An evaluator returning itself
Effectable.Class returned its own instance to the interpreter, repeating forever. Delegate to the subclass’s asEffect() instead.
112344Base.prototype = Prototype({Base.prototype = Prototype<Class<any, any, any>>({ label: "Effectable", evaluate(_) { return this return this.asEffect()39.Writing zero bytes
An empty buffer produced zero bytes of progress and was classified as a stalled write. It is already complete; return before entering the loop.
11return this.writeAllChunk(buffer)return buffer.length === 0 ? Effect.void : this.writeAllChunk(buffer)40.Attribute order
The same metric labels in a different insertion order created a second series. Sort a fresh entries array before building the key.
1123return JSON.stringify(Array.isArray(attributes) ? attributes : Object.entries(attributes))const entries = Array.isArray(attributes) ? [...attributes] : Object.entries(attributes)entries.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)return JSON.stringify(entries)41.Child tables in separate entries
Two [[servers]] entries could not each declare [servers.tls]: their identical paths looked like a duplicate. Track the resolved table object, not the textual path.
121234566788const pathKey = JSON.stringify(path)if (!array && this.explicitTables.has(pathKey)) {const table = this.resolveTable(path, array)if (!array && this.explicitTables.has(table)) { this.fail(`Cannot redefine table '${path.join(".")}'`)}if (!array) { this.explicitTables.add(pathKey) this.explicitTables.add(table)}this.current = this.resolveTable(path, array)this.current = table42.Folded YAML paragraphs
YAML folding added an extra paragraph newline and turned breaks around indented passages into spaces. Track blank lines and indentation when choosing each separator.
1234123456789101112131415161718819 for (let index = 0; index < content.length; index++) { output += content[index] if (index < content.length - 1) { output += content[index].length === 0 || content[index + 1].length === 0 ? "\n" : " " let blankLines = 0 let previousMoreIndented: boolean | undefined for (const line of content) { if (line.length === 0) { blankLines++ } else { const moreIndented = line.startsWith(" ") const hardBreak = moreIndented || previousMoreIndented === true if (previousMoreIndented === undefined) output += "\n".repeat(blankLines) else if (blankLines === 0) output += hardBreak ? "\n" : " " else output += "\n".repeat(blankLines + (hardBreak ? 1 : 0)) output += line blankLines = 0 previousMoreIndented = moreIndented } } output += "\n".repeat(blankLines)}if (chomp === "keep") return `${output}\n`if (chomp === "keep") return output.endsWith("\n") ? output : `${output}\n`43.Optional alternative flags
Flag.optional rejected Flag.orElse trees before parsing. An unused single-leaf assertion was the only obstacle; removing it lets the existing parser handle alternatives.
123412345678912const parse: Parse<Option.Option<A>> = Effect.fnUntraced(function*(args) { getUnderlyingSingleOrThrow(param)
return yield* param.parse(args).pipe(const parse: Parse<Option.Option<A>> = (args) => param.parse(args).pipe( Effect.map(([leftover, value]) => [leftover, Option.some(value)] as const), // Catch both MissingOption (for flags) and MissingArgument (for positional arguments) Effect.catchTags({ MissingOption: () => Effect.succeed([args.arguments, Option.none()] as const), MissingArgument: () => Effect.succeed([args.arguments, Option.none()] as const) }) )})44.Range on a HEAD request
A HEAD request with an unsatisfiable Range returned 416. Range applies only to GET; HEAD should return the full metadata without a body.
11const rangeHeader = request.headers["range"]const rangeHeader = request.method === "GET" ? request.headers["range"] : undefined45.Errors during finalization
A writable’s _final error left the sink waiting for a finish event that would never arrive. Keep the error listener active through finalization.
1234567123456714891011121314181516171819202122Effect.raceFirst(Effect.callback<never, E>((resume) => { const onError = (error: unknown) => resume(Effect.fail(options.onError(error))) options.writable.once("error", onError) return Effect.sync(() => { options.writable.off("error", onError) })})),options.endOnDone !== false ? Pull.catchDone((_) => { if ("closed" in options.writable && options.writable.closed) { return Cause.done(_) } return Effect.callback<never, E | Cause.Done<unknown>>((resume) => { const onFinish = () => resume(Cause.done(_)) options.writable.once("finish", () => resume(Cause.done(_))) options.writable.once("finish", onFinish) options.writable.end() return Effect.sync(() => { options.writable.off("finish", onFinish) }) }) }) : identity identity,Effect.raceFirst(Effect.callback<never, E>((resume) => { const onError = (error: unknown) => resume(Effect.fail(options.onError(error))) options.writable.once("error", onError) return Effect.sync(() => { options.writable.off("error", onError) })}))46.An unsupported form reader
The adapter called an unimplemented Undici formData() method. Parse the cached bytes with native Response.formData(), preserving headers for multipart boundaries.
112345678234567891011101112return this.formDataBody ??= Effect.flatMap(this.arrayBuffer, (body) =>return this.formDataBody ??= Effect.tryPromise({ try: () => this.source.body.formData() as Promise<FormData>, catch: (cause) => new Error.HttpClientError({ reason: new Error.DecodeError({ request: this.request, response: this, cause Effect.tryPromise({ try: () => new globalThis.Response(body, { headers: this.headers }).formData(), catch: (cause) => new Error.HttpClientError({ reason: new Error.DecodeError({ request: this.request, response: this, cause }) }) })}).pipe(Effect.cached, Effect.runSync) })).pipe(Effect.cached, Effect.runSync)47.A discarded domain option
The MSSQL adapter accepted an NTLM domain but never passed it to the driver. Forward the field with the other authentication options.
1domain: options.domain,48.Binding byte arrays
The driver rejected automatically bound byte arrays because its binary validator expected a Buffer. Adapt the array while preserving its byte offset and length.
1234567891011const byteArrayParameterType: DataType = { ...Tedious.TYPES.VarBinary, validate(value, collation, options) { return Tedious.TYPES.VarBinary.validate( Buffer.isBuffer(value) ? value : Buffer.from(value.buffer, value.byteOffset, value.byteLength), collation, options ) }}
49.A replaced reason phrase
Custom statusText, including an empty string, disappeared on the way to Node. Pass it through every writeHead call instead of letting Node substitute its default.
11nodeResponse.writeHead(response.status, headers)nodeResponse.writeHead(response.status, response.statusText, headers)50.An ignored SSL override
A URL with sslmode=prefer was rejected before an explicit ssl option could override it. Permit that mode when the caller has already made the SSL choice.
1if (hasExplicitSsl) break51.Text from a binary response
XHR forbids responseText in arraybuffer mode. Decode the returned bytes for text and JSON, and pass them directly to stream readers.
1234567if (this.source.responseType === "arraybuffer") { return this._textEffect = this.arrayBuffer.pipe( Effect.map((buffer) => new TextDecoder().decode(buffer)), Effect.cached, Effect.runSync )}52.A stale Vue snapshot
Switching from a ref containing 1 to one containing 10 still displayed 1 until another write. Subscribe to the new ref, then publish its current value.
1value.value = ref.value53.Negative counter deltas
Updates [5, -2, 4] exported as [5, 3, 4]: a legitimate decrease was mistaken for a reset. Apply reset correction only to incremental counters.
11234567899 if (reportValue < BigInt(0)) { if (state.state.incremental && reportValue < BigInt(0)) { reportValue = currentCount }} else { const curr = Number(currentCount) const prev = Number(previousCount) reportValue = curr - prev // Handle reset if (reportValue < 0) { if (state.state.incremental && reportValue < 0) {54.A field that would not delete
Replacing an optional focused field with an empty object restored its old value. Preserve only fields outside the focus before merging the replacement.
112344 return this.compose(makeLens(Struct.pick(keys), (p, a) => ({ ...a, ...p }))) return this.compose(makeLens(Struct.pick(keys), (p, a) => ({ ...Struct.omit(a, keys), ...p })))}omit(keys: any) { return this.compose(makeLens(Struct.omit(keys), (o, a) => ({ ...a, ...o }))) return this.compose(makeLens(Struct.omit(keys), (o, a) => ({ ...Struct.pick(a, keys), ...o })))55.An array hole instead of removal
Deleting tuple key "1" left [1, <hole>, 3], while numeric key 1 produced [1, 3]. Canonical string indices must use splice too.
121234567if (Array.isArray(copy) && typeof key === "number") { copy.splice(key, 1)const index = typeof key === "symbol" ? NaN : Number(key)if ( Array.isArray(copy) && (typeof key === "number" || (String(index) === key && Number.isInteger(index) && index >= 0 && index < 0xFFFFFFFF))) { copy.splice(index, 1)56.An array-valued element
ArrayEnsure confused a single tuple with the outer array. Preserve which branch decoded the input, so [1, 2] becomes [[1, 2]] and round-trips correctly.
1212345456787910return Union([schema, ArraySchema(schema)]).pipe(decodeTo( ArraySchema(toType(schema)),const many = ArraySchema(schema)const to = ArraySchema(toType(schema))const one = decodeTo( Tuple([Unknown]), SchemaTransformation.transform({ decode: Arr.ensure, encode: (array) => array.length === 1 ? array[0] : array decode: (value) => [value] as const, encode: ([value]) => value }))))(schema)return make(Union([one, many]).pipe(decodeTo(to)).ast, { from: Union([schema, many]), to })57.Waiting past a line ending
A chunk ending in CR already contained a complete line, but the parser pulled again and could encounter an unrelated failure. Emit immediately; only suppress a following LF.
12345657891011121213 pushLine(str.substring(from, indexOfCR)) if (str.length === indexOfCR + 1) { midCRLF = true from = str.length indexOfCR = -1 } else { pushLine(str.substring(from, indexOfCR)) from = indexOfCR + (indexOfLF === indexOfCR + 1 ? 2 : 1) indexOfCR = str.indexOf("\r", from) indexOfLF = str.indexOf("\n", from) } }}stringBuilder = stringBuilder + str.substring(from, str.length - (midCRLF ? 1 : 0))stringBuilder = stringBuilder + str.substring(from)58.Three rows became four
A query with limit(3) and chunkSize: 2 returned four rows. Each continuation must request only the remaining number of rows.
1limitValue: limit === undefined ? chunkSize : Math.min(chunkSize, limit - total),59.An off-by-one allocation
The exclusion scan stopped before reaching ring index zero. One node could receive every shard while the other stayed unused. Include the final search radius.
11for (let i = 1; i < range; i++) {for (let i = 1; i <= range; i++) {60.Half a Unicode character
Parsing echo \u{1F600} tail split the emoji and corrupted the next argument. An astral character occupies two UTF-16 units; advance the cooked cursor accordingly.
12234567// - Advancing the template index an extra code unit for astral code points// - Advancing the raw template index past the unicode escape sequencerawIndex = rawTemplate.indexOf("}", rawIndex + 3)const end = rawTemplate.indexOf("}", rawIndex + 3)if (parseInt(rawTemplate.slice(rawIndex + 3, end), 16) > 0xffff) { templateIndex += 1}rawIndex = end61.Media-type parameters
text/html; charset=utf-8 had no known extensions, although text/html worked. Strip parameters and whitespace before looking up the media type.
11return Option.fromUndefinedOr(typeToExtensions.get(type.toLowerCase()))return Option.fromUndefinedOr(typeToExtensions.get(type.split(";")[0].trim().toLowerCase()))62.A default that never applied
An omitted variadic argument was classified as invalid, so its default never applied. Zero values means missing; a partially supplied list below the minimum remains invalid.
12123456345678return yield* count === 0 ? new CliError.MissingArgument({ argument: single.name })return yield* new CliError.InvalidValue({ option: single.name, value: `${count} values`, expected: `at least ${minValue} value${minValue === 1 ? "" : "s"}`, kind: single.kind}) : new CliError.InvalidValue({ option: single.name, value: `${count} values`, expected: `at least ${minValue} value${minValue === 1 ? "" : "s"}`, kind: single.kind })63.One slash too many
A route matched under /api/, then exposed users instead of /users to its handler. Prefix removal must use the same trailing-slash normalization as registration.
1prefix = removeTrailingSlash(prefix as PathInput)64.Stale response metadata
An unannotated replacement body inherited the source’s 429 and text encoding instead of the error defaults 500 and JSON. Writing undefined annotations clears the stale values.
1212...(status !== undefined ? { httpApiStatus: status } : undefined),...(encoding !== undefined ? { "~httpApiEncoding": encoding } : undefined)httpApiStatus: status,"~httpApiEncoding": encoding65.Two encoders, two statuses
A stream inside WithHeaders could use a different status from its header encoder and defect. Both encoders must resolve the status from the original wrapped schema.
12123456778const streamSchema = getStreamSuccessSchema(endpoint)if (streamSchema === undefined) {const successSchema = getStreamSuccessSchema(endpoint)if (successSchema === undefined) { return undefined}
const streamSchema = successSchema.bodyconst hasBuffered = hasBufferedSuccess(endpoint)const status = HttpApiSchema.getStatusStream(streamSchema)const status = HttpApiSchema.getStatusSuccessSchema(successSchema.schema)66.Missing response hooks in tests
The in-memory HTTP client skipped registered pre-response handlers. Headers and cookies added by those hooks disappeared. Run the chain before converting the response.
1234Effect.flatMap((response) => { const preResponse = preResponseHandler.requestPreResponseHandlers.get(serverRequest.source) return preResponse === undefined ? Effect.succeed(response) : preResponse(serverRequest, response)}),67.An uncatchable storage error
Durable Object SQL streams exposed native storage errors as defects instead of SqlError. Classify driver failures before row transformations, leaving user-code defects alone.
123456Stream.catchCauseFilter(Cause.findDefect, (defect) => Stream.fail( new SqlError({ reason: classifyError(defect, "Failed to execute statement", "execute") }) )),68.Cleaning up the same key twice
A repeated query key made cleanup access a handler set already deleted by its first occurrence. Skip registrations that are no longer present.
112const set = handlers.get(resolvedKeys[i])!const set = handlers.get(resolvedKeys[i])if (set === undefined) continue69.Encoding the wrong result branch
A known tool failure could be encoded by an overlapping success schema. Select the encoder using isFailure, rather than asking a success-first union to choose again.
121234567910118910151611121314151617181921 readonly decodeResult: (u: unknown) => Effect.Effect<unknown, Schema.SchemaError> readonly encodeResult: (u: unknown) => Effect.Effect<unknown, Schema.SchemaError> readonly encodeResult: (u: unknown, isFailure: boolean) => Effect.Effect<unknown, Schema.SchemaError>}>()
const getSchemas = (tool: Tool.Any) => { let schemas = schemasCache.get(tool) if (Predicate.isUndefined(schemas)) { const handler = services.mapUnsafe.get(tool.id)! as Tool.Handler<any> const resultSchema = tool.failureMode === "return" ? Schema.Union([tool.successSchema, tool.failureSchema, AiError.AiError]) : tool.successSchema const decodeParameters = Schema.isSchema(tool.parametersSchema) ? Schema.decodeUnknownEffect(tool.parametersSchema) as any : (u: unknown) => Effect.succeed(u) const decodeResult = Schema.decodeUnknownEffect(resultSchema) as any const encodeResult = Schema.encodeUnknownEffect(resultSchema) as any const encodeSuccess = Schema.encodeUnknownEffect(tool.successSchema) as any const encodeFailure = Schema.encodeUnknownEffect(tool.failureSchema) as any const encodeAiError = Schema.encodeUnknownEffect(AiError.AiError) const encodeResult = (u: unknown, isFailure: boolean) => !isFailure ? encodeSuccess(u) : AiError.isAiError(u) ? encodeAiError(u) : encodeFailure(u) schemas = { context: handler.context, handler: handler.handler, decodeParameters, decodeResult,70.A missing semicolon
A generated doctest assertion could swallow the following array expression or IIFE as a continuation of its call. Terminate the generated statement.
11`${alias}(${source.slice(expression.start, expression.end)}, ${expected})``${alias}(${source.slice(expression.start, expression.end)}, ${expected});`71.Overlapping metric intervals
Delta values covered the latest interval, but their timestamps kept the first interval’s start. Use the previous collection time for each new delta point.
11const startTime = this.startTimeFor(descriptor.name, intervalStartTime)const startTime = isDelta ? intervalStartTime : this.startTimeFor(descriptor.name, intervalStartTime)72.Normalizing a value twice
The normalized reason tool-calls was decoded again as if it were wire-format tool_calls, producing unknown and leaving calls unflushed. Assign the normalized value directly.
11finishReason = resolveFinishReason("tool-calls")finishReason = "tool-calls"73.An undefined write flag
Forwarding { flag: undefined } erased the file sink’s write default. Spread the options first, then apply flag ?? "w".
11impl.open(path, { flag: "w", ...options }),impl.open(path, { ...options, flag: options?.flag ?? "w" }),74.The wrong address kind
A Unix-domain server reported a TCP address, yielding http://undefined:undefined. Report its Unix socket path instead.
1213address: "unix" in options && options.unix !== undefined ? { _tag: "UnixAddress", path: options.unix }address: { _tag: "TcpAddress", port: server.port!, hostname: server.hostname! }, : { _tag: "TcpAddress", port: server.port!, hostname: server.hostname! },75.A reply without its envelope
sendUnsafe({ value: 42 }) reached the parent as undefined. Both public reply methods must add the data envelope expected by the parent decoder.
112345657const sendUnsafe = WorkerThreads.parentPortconst sendRaw = WorkerThreads.parentPort ? (_portId: number, message: any, transfers?: any) => WorkerThreads.parentPort!.postMessage(message, transfers) : (_portId: number, message: any, _transfers?: any) => process.send!(message)const sendUnsafe = (_portId: number, message: O, transfers?: ReadonlyArray<unknown>) => sendRaw(_portId, [1, message], transfers)const send = (_portId: number, message: O, transfers?: ReadonlyArray<unknown>) => Effect.sync(() => sendUnsafe(_portId, [1, message], transfers as any)) Effect.sync(() => sendUnsafe(_portId, message, transfers))76.A URI matched as a path
An HTTPS resource template retained its origin during registration but lost it during lookup. Prefix both inputs with / so the router treats the full URI consistently.
11233 router.on("GET", uri as any, value) router.on("GET", `/${uri}`, value)}const find = (uri: string) => router.find("GET", uri)const find = (uri: string) => router.find("GET", `/${uri}`)77.A colliding SQL alias
A joined reply_id overwrote an acknowledgement’s own reply ID. Use the distinct reply_reply_id alias expected by the shared decoder.
11SELECT m.*, r.id as reply_id, r.kind as reply_kind, r.payload as reply_payload, r.sequence as reply_sequenceSELECT m.*, r.id as reply_reply_id, r.kind as reply_kind, r.payload as reply_payload, r.sequence as reply_sequence78.A zero durability threshold
An explicit inMemoryThreshold: 0 became the 60-second default, keeping positive sleeps in memory. Only undefined should select that default.
11const inMemoryThreshold = options.inMemoryThresholdconst inMemoryThreshold = options.inMemoryThreshold !== undefined79.A cause that looked absent
Error causes such as 0, false and an empty string disappeared from diagnostic output. Test whether the cause is defined, not whether it is truthy.
11output = v instanceof Error && v.cause ? `${s} (cause: ${recur(v.cause, d)})` : soutput = v instanceof Error && v.cause !== undefined ? `${s} (cause: ${recur(v.cause, d)})` : s80.A port that never started
A MessagePort listener alone does not deliver queued messages. Call start() after registration so the SQLite client receives readiness and its worker receives requests.
1options.port.start?.()