Skip to content

useAwaitThenable rejects Fastify listen without a callback since 2.5.13 #12176

Open

Description

@arcxyz

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.ts

This 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

  1. changed the title [-]💅 useAwaitThenable rejects Fastify listen without a callback since 2.5.13[/-] [+] useAwaitThenable rejects Fastify listen without a callback since 2.5.13[/+] on Oct 6, 2026
  2. biome-cookie commented on Oct 6, 2026

    @biome-cookie

    Hello @arcxyz, please provide a minimal reproduction. You MUST use one of the following options:

    • Provide a link to our playground, if it's applicable.
    • Provide a link to GitHub repository. To easily create a reproduction, you can use our interactive CLI via npm create @biomejs/biome-reproduction

    These 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 repro will be closed if they have no activity within 3 days.

  3. arcxyz commented on Oct 7, 2026

    @arcxyz
    Author

    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 lint

    The same two-line repro.ts passes with Biome 2.5.12 and reports useAwaitThenable on line 2 with 2.5.15. Both versions use the same locked Fastify 5.12.5 installation and configuration.

    npm run lint:controls confirms that 2.5.15 accepts await Promise.resolve(...) and reports an awaited string. All commands lint the inputs without executing them.

  4. added
    S-Needs triageStatus: this issue needs to be triaged
    L-JavaScriptLanguage: JavaScript and super languages
    S-Bug-confirmedStatus: report has been confirmed as a valid bug
    and removed
    S-Needs reproStatus: needs a reproduction
    S-Needs triageStatus: this issue needs to be triaged
    on Oct 7, 2026
  5. dyc3 commented on Oct 7, 2026

    @dyc3
    Contributor

    Thank you, I can reproduce it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-Type-InferenceArea: type inferenceL-JavaScriptLanguage: JavaScript and super languagesS-Bug-confirmedStatus: report has been confirmed as a valid bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions