Repository navigation
useAwaitThenable rejects Fastify listen without a callback since 2.5.13 #12176
Description
Environment information
Tested on 2026-10-06 with Node.js 24.21.0, Fastify 5.12.5 and Biome 2.5.12, 2.5.13, 2.5.14 and 2.5.15. Both npm's latest tag and GitHub's latest release point to 2.5.15.
biome rage --linter (2.5.15)
CLI:
Version: 2.5.15
Color support: false
Platform:
CPU Architecture: x86_64
OS: linux
Environment:
BIOME_DISTRIBUTION: npm
BIOME_LOG_FILE: unset
BIOME_LOG_PATH: unset
BIOME_LOG_PREFIX_NAME: unset
BIOME_LOG_LEVEL: unset
BIOME_LOG_KIND: unset
BIOME_CONFIG_PATH: unset
BIOME_THREADS: unset
BIOME_WATCHER_KIND: unset
BIOME_WATCHER_POLLING_INTERVAL: unset
BIOME_BINARY: unset
RUST_BACKTRACE: unset
NO_COLOR: 1
TERM: dumb
JS_RUNTIME_VERSION: v24.21.0
JS_RUNTIME_NAME: node
NODE_PACKAGE_MANAGER: unset
Biome Configuration:
Status: Loaded successfully.
Path: biome.json
Formatter enabled: true
Linter enabled: true
Assist enabled: true
VCS enabled: false
HTML full support enabled: unset
Linter:
JavaScript enabled: unset
JSON enabled: unset
CSS enabled: unset
GraphQL enabled: unset
Recommended: unset
Enabled rules:
nursery/useAwaitThenable
Workspace:
Open Documents: 0
Rule name
lint/nursery/useAwaitThenable
Description
Starting with 2.5.13, Biome reports await Fastify().listen({ port: 0 }) as awaiting a non-Promise. The same file, dependency installation and configuration pass with 2.5.12. The diagnostic remains in 2.5.14 and 2.5.15.
This is the no-callback overload, which returns Promise<string>. The callback overload returns void and should be reported if awaited. Those cases are distinguished in the controls below.
Playground link / reproduction
This reproduction uses the installed Fastify declarations, so here are complete CLI reproduction steps instead of a playground example with an unresolved import. The files are lint inputs; do not execute the control files.
Create these files in an empty directory.
package.json:
{
"name": "biome-fastify-await-reproduction",
"private": true,
"type": "module",
"dependencies": { "fastify": "5.12.5" },
"devDependencies": {
"biome-2-5-12": "npm:@biomejs/biome@2.5.12",
"biome-2-5-13": "npm:@biomejs/biome@2.5.13",
"biome-2-5-14": "npm:@biomejs/biome@2.5.14",
"biome-2-5-15": "npm:@biomejs/biome@2.5.15",
"typescript": "7.0.2",
"@types/node": "24.19.1"
}
}biome.json:
{
"linter": {
"rules": {
"preset": "none",
"nursery": { "useAwaitThenable": "error" }
}
}
}repro.ts (the complete failing case):
import Fastify from "fastify";
await Fastify().listen({ port: 0 });Install once and compare the binaries against the same files:
npm install --ignore-scripts --no-audit --no-fund
for version in 12 13 14 15; do
node "node_modules/biome-2-5-$version/bin/biome" --version
node "node_modules/biome-2-5-$version/bin/biome" lint repro.ts
done| Biome | Result for repro.ts |
|---|---|
| 2.5.12 | Exit 0, no diagnostics |
| 2.5.13 | Exit 1, useAwaitThenable at line 2 |
| 2.5.14 | Exit 1, useAwaitThenable at line 2 |
| 2.5.15 | Exit 1, useAwaitThenable at line 2 |
The diagnostic is:
repro.ts:2:1 lint/nursery/useAwaitThenable
× `await` used on a non-Promise value.
1 │ import Fastify from "fastify";
> 2 │ await Fastify().listen({ port: 0 });
The original application form, const app = Fastify(); await app.listen({ host: "127.0.0.1", port: 0 });, has the same result.
Controls and expected result
controls.ts:
await Promise.resolve("valid");
await "not a Promise";
export {};All four versions report only line 2. Promise.resolve is correctly accepted and the actual non-Promise is correctly rejected.
fastify-controls.ts:
import Fastify from "fastify";
const app = Fastify();
const listening: Promise<string> = app.listen({ host: "127.0.0.1", port: 0 });
await listening;
await app.close();
await app.listen({ port: 0 }, () => {});2.5.13, 2.5.14 and 2.5.15 report only line 7 (the callback overload, correctly). They accept the explicitly typed Promise<string> and app.close(). 2.5.12 reports nothing in this control file, including missing the callback case; its clean result on the original reproduction alone is therefore not proof that it fully understands Fastify's overloads.
The explicit assignment to Promise<string> also type-checks:
node node_modules/typescript/bin/tsc --noEmit --strict --skipLibCheck \
--module nodenext --target esnext repro.ts controls.ts fastify-controls.tsThis exits 0. Fastify 5.12.5's types/instance.d.ts declares:
listen(opts: FastifyListenOptions, callback: (err: Error | null, address: string) => void): void;
listen(opts?: FastifyListenOptions): Promise<string>;
listen(callback: (err: Error | null, address: string) => void): void;Expected: accept the awaited no-callback call, while retaining diagnostics for actual non-Promise awaits. Removing the valid await changes the startup ordering; disabling the rule is not a fix.
I have not bisected a specific Biome commit or established the internal cause.
Code of Conduct
- I agree to follow Biome's Code of Conduct.
Activity
[-]💅 useAwaitThenable rejects Fastify listen without a callback since 2.5.13[/-][+] useAwaitThenable rejects Fastify listen without a callback since 2.5.13[/+]biome-cookie commentedon Oct 6, 2026
Hello @arcxyz, please provide a minimal reproduction. You MUST use one of the following options:
npm create @biomejs/biome-reproductionThese formats help us triage and fix issues faster. Failing to use these formats may cause your issue to be ignored or closed.
Issues marked with
S-Needs reprowill be closed if they have no activity within 3 days.arcxyz commentedon Oct 7, 2026
Minimal reproduction: https://github.com/arcxyz/biome-fastify-await-repro
git clone https://github.com/arcxyz/biome-fastify-await-repro.git cd biome-fastify-await-repro npm ci --ignore-scripts --no-audit --no-fund npm run lint:baseline npm run lintThe same two-line
repro.tspasses with Biome 2.5.12 and reportsuseAwaitThenableon line 2 with 2.5.15. Both versions use the same locked Fastify 5.12.5 installation and configuration.npm run lint:controlsconfirms that 2.5.15 acceptsawait Promise.resolve(...)and reports an awaited string. All commands lint the inputs without executing them.dyc3 commentedon Oct 7, 2026
Thank you, I can reproduce it.