Troubleshooting
The errors and surprises you're most likely to hit when you first build a program with bun compile: the message, the cause and the fix
When you build#
The build stops at a type error#
const port: number = "3000";
console.log(port);bun compile ./port.tserror: TS2322: Type 'string' is not assignable to type 'number'.
at /path/to/port.ts:1:7bun run and bun build remove your types and never check them, so a project can carry type errors for years. bun compile relies on the types, so a type error stops the build.
Run bun check --aot ./port.ts and fix what it reports. It reports every error that the build reports, and builds nothing. For an error that you can't fix now, put // @ts-expect-error above the line. The build accepts it, as tsc does.
Could not find a declaration file for module#
error: TS7016: Could not find a declaration file for module 'no-types'. '/path/to/node_modules/no-types/index.js' implicitly has an 'any' type.
Try `bun add -d @types/no-types` if it exists or add a new declaration (.d.ts) file containing `declare module 'no-types';`The package is JavaScript and ships no types. It's a type error like any other, so it stops the build. Do what the message says.
Bun's type declarations aren't installed#
console, process, Bun, fetch and modules such as bun:sqlite and node:fs are declared by @types/bun. Without it, TypeScript doesn't know them.
console.log("hello");bun check ./hello.tshello.ts(1,1): error TS2584: Cannot find name 'console'. Do you need to change your target library? Try changing the 'lib' compiler option to include 'dom'.bun compile still builds the program, and it still knows the types of Bun's and Node.js's APIs. Bun carries a copy of @types/bun and of the two packages it depends on, @types/node and undici-types. A build reads the copy when @types/bun isn't installed, and so do bun check --aot, bun run --aot and bun test --aot. Plain bun check doesn't.
What you install wins over the copy. With @types/bun in node_modules, the build doesn't read the copy at all. With only @types/node or only bun-types installed, the build reads yours and takes the rest from the copy. If your tsconfig.json lists a package under "types" that isn't installed, the build warns you (TS2688) and continues.
The copy is there so the compiler knows Bun's APIs, not to hold your project to types it hasn't installed. If the program has a type error with the copy, the build starts over with whatever Bun provides typed as any. Code that uses it still works but runs slower. These aren't errors in that second build:
- a global that Bun provides, and
require,module,exports,__filenameand__dirname - an import of one of Bun's modules, and an import of a file that the build loads, such as
./note.txt - a member of
Bun, of the module"bun", ofimport.metaor ofError, such asError.captureStackTrace() - a member that Bun adds to a class of TypeScript's DOM library, such as
blob.json()orconsole.write() - in a class that extends a class Bun provides, such as
EventorEventEmitter: arguments for the constructor it inherits,overrideon a member, and an inherited member read asthis.typeundernoPropertyAccessFromIndexSignature - a parameter without a type in a callback that you pass to something Bun provides, as in
process.on("exit", code => {}) - in a project without a
tsconfig.json, a parameter or a variable that's implicitlyany
A name that Bun doesn't provide, such as document, is still an error. So is an import type from a package that isn't installed, and every other type error. Install the declarations, and Bun's globals and modules have types in your editor and in bun check too.
bun add -d @types/bunA package has type errors, so it's compiled without types#
warn: TS100009: 'typed-pkg' has 2 type errors (the first at /path/to/node_modules/typed-pkg/index.ts:2:9), so it is compiled without its types. It still works, but runs slower.
2 | const n: number = text;
^
note: TS2322: Type 'string' is not assignable to type 'number'.A dependency ships TypeScript source files, and they don't pass the type checker in your project. The build doesn't stop. It compiles that package like JavaScript, which behaves the same and runs slower.
The warning shows the first error only. bun check lists all of them.
bun check --all --no-pretty ./app.tsCommon causes:
- A
declare modulein your project, or in another package, changes the interfaces of the package. @types/bunand the DOM or Node.js types disagree about a name such asReadableStreamorEvent.- The package has a
"bun"export condition. The bundler takes it and the type checker doesn't. Add"customConditions": ["bun"]tocompilerOptions. - The package relies on a declaration file of its own that nothing imports. List that file under
"files"in yourtsconfig.json.
bun compile can't cross-compile#
error: bun compile can't cross-compile. It only builds for the platform it runs on
note: run this on the target platform, or pass --no-aot to bundle your code with the Bun runtime instead, like bun build --compileDrop --target and run the build where the executable runs: in CI on a runner of that platform, or in a container. Or pass --no-aot, and the executable contains JavaScript for the JIT.
The file passed to --compile-executable-path can't run the compiled code#
error: ./other-bun can't run code compiled by this Bun: it is revision 1f2e3d4c of Bun and this is 9a8b7c6d
note: use a build of the same revision of Bun with the same configurationCompiled code relies on the layout of the runtime it is attached to. The file can be another build of the same revision of Bun: stripped, linked differently, position-independent or not, or built with other optimization flags. It can't be another version, another platform, or a build with different features. The message names the first difference.
--aot does not apply to a package.json script#
error: --aot does not apply to a package.json script (dev). It compiles a file: put --aot on the bun command inside the scriptbun run --aot takes a file. Put the flag in the script.
{
"scripts": {
"dev": "bun server.ts"
"dev": "bun --aot server.ts"
}
}This build of Bun doesn't support --trim#
error: this build of Bun doesn't support --trim (NoTrimTable)--trim needs a bun that was linked for it. A bun that you built yourself may not be.
When the program runs#
Cannot assign to Array.prototype.push because built-in objects are read-only#
TypeError: Cannot assign to Array.prototype.push because built-in objects are read-only in a compiled executable (bun compile differs from bun run here: https://bun.com/docs/compiler/compatibility)Your program or a dependency changes a built-in object. The message has the object and the property, and the stack trace points to the line.
- In your own code, use a function or a subclass. See Built-in objects are read-only.
- In a dependency, look it up in Packages that change built-in objects.
core-js,sass,@colors/colors,chai.should()and the fake timers ofsinonare there.
Cannot find module, on another machine#
error: Cannot find module './plugins/csv.ts' from '/$bunfs/root/app'Your program imports a module from a dynamic path. On your machine the file is on disk. On the server it isn't. Pass such files to bun compile as additional entry points. See Dynamic imports.
Cannot load a file: it isn't in this executable#
error: Cannot load '/home/me/workers/late.ts': it isn't in this executable, which only runs code that was compiled into it. Pass the file to bun compile as another entry point, or build with --aot-allow-code-from-disk.A dynamic import() or require() can only load code that's in the executable. The file exists, but it wasn't compiled. To load it from disk, build with --aot-allow-code-from-disk. See Code that isn't in the executable.
Something was not compiled into this executable#
Error: None of console._stderr, console._stdout, process.stderr or process.stdout was compiled into this executable because your code never uses them. To keep them, use one of them somewhere in your code. (bun compile differs from bun run here: https://bun.com/docs/compiler/compatibility)A string passed to eval or new Function uses an API that your own code never uses. Use it somewhere in your code. See Code from strings.
bun:ffi: a compiled executable can have 256 JSCallbacks open at a time#
The entry points that native code calls are compiled into the executable, so there's a fixed number of them. callback.close() gives one back. The rest of bun:ffi works as in bun run: dlopen, linkSymbols, CFunction and JSCallback.
Cannot compile C code because this process can't create executable memory (PR_SET_MDWE)#
On Linux 6.7 or later, the executable tells the kernel at startup that none of its memory will ever be both writable and executable, and that no memory will become executable. All of its code comes from files: the executable itself, shared libraries and native addons. cc() compiles C into memory, so it can't work. Compile the C file when you build and load the library with dlopen(). Child processes aren't restricted.
A sandbox or launcher can make the same request before it starts the executable (PR_SET_MDWE without PR_MDWE_NO_INHERIT, on Linux 6.3 or later). The executable runs the same way and leaves the request as it found it. In that case its child processes are restricted too, so a child with a JIT, such as node, may not start.
--preload can't be used with this executable#
error: --preload can't be used with this executable because it only runs code that was compiled into it. Rebuild it with --compile-inspector to load other codeAn option in BUN_OPTIONS would load code that isn't in the executable. Import the file from your entry point so that it's compiled too. A preload in bunfig.toml is ignored.
error: BUN_INSPECT can't be used with this executable. Rebuild it with --compile-inspectorA debugger needs a flag of its own. See Options that load code.
Breakpoints never bind#
In an executable built with --compile-inspector, the inspector connects, and its console works. Breakpoints, stepping and debugger statements don't. Debug from your source files with bun --inspect-brk ./app.ts. To see whether a bug only shows in compiled code, compare with the executable.
My error tracker shows new groups#
Where bun run quotes your source in a message, as in null is not an object (evaluating 'o.a.c'), an executable quotes the name of the property, as in (evaluating 'null.c'), or nothing. See Error messages.
Performance#
The executable is slower than bun run#
Startup and memory improve for every server in What to expect. Requests per second go up for some and down for others. Look for these causes, in this order.
Most of the hot code is untyped code from npm. The Dependencies: line at the end of a build has their share, and bun compile --explain lists the causes. The count of an entry that only affects dependencies ends with in dependencies.
Dependencies: 0% of property accesses are one load: 0 free, 0 tested, 2 by name (2)4 | console.log(label(JSON.parse(text)));
^
note: 1. unknown-object: An object from untyped code is used as this type here, so it can have any shape.
at app.ts:4:24
note: 2 of 2 untyped accesses in dependencies
note: why (external-data): The value comes from outside the program (JSON.parse, response.json(), structuredClone, a message).
note: fix: build a new object of this type from it where it enters typed code.
note: https://bun.com/docs/compiler/explain#unknown-objectA JavaScript package isn't untyped because it's JavaScript. The compiler tracks its objects as it tracks yours. So look first at what your code passes to the package: an object that comes from JSON.parse, as here, is unknown inside the package too. For the rest, a package can point bun compile at its TypeScript with the "aot" condition.
A guard fails in hot code. A value that doesn't match its declared type sends the function to its slow path, every time. Nothing is printed, because nothing goes wrong. Find the guards that fail shows how to list them and how to log them.
A library generates code at run time. Bun interprets that code. See Code from strings.
The program runs WebAssembly that it doesn't import. A .wasm file that your program imports is compiled. Bun interprets every other module, such as bytes that your program downloads. The executable has no JIT, so a tight loop runs about 20 times slower there. See WebAssembly.
An --external package is loaded from disk. Bun interprets it, and everything it loads. See Code that isn't in the executable.
One line makes many accesses untyped. An object from JSON.parse that's used as one of your types slows down every access that it can reach. An object that's passed to code that isn't in the executable, or changed with delete or Object.setPrototypeOf, slows down every access to its type. bun compile --explain lists such lines, worst first, with a fix for each. See Get more of your program typed.
The executable is larger than with --compile#
It contains the same copy of Bun, plus the machine code of your program.
--trimremoves the Bun and Node.js APIs that your code never calls.--no-compile-function-sourceremoves the source code of your functions.toString()on a function then returns[native code]. The executable rejects the options that load code, with--compile-inspectortoo.
A trimmed executable is still large#
Something in your program, usually eval or a dynamic require(), could reach any API, so --trim kept all of them. --explain=trim shows what on its blocked by lines. See See what the compiler found. Make it smaller lists what else you can do.
Report a bug#
If a program passes bun check --aot and behaves differently from bun run in a way that these pages don't describe, that's a bug in Bun. So is a crash, or exit code 70 from a trimmed executable. Open an issue with the smallest program that shows it.