Docs · Inspector
◍ Debugging with the inspector
Oam.js embeds the V8 Inspector and speaks the Chrome DevTools Protocol over a WebSocket, the same way Node does. Any CDP client attaches: Chrome DevTools, VS Code, or something you wrote.
Turning it on
oam run --inspect app.ts
oam run --inspect-brk app.ts # wait for a debugger, break on line 1
oam serve --inspect server.ts
oam run --inspect=9230 app.ts # a bare port binds 127.0.0.1
oam run --inspect=0.0.0.0:9229 app.ts
| Flag | What it does |
|---|---|
--inspect[=[host:]port] |
Start the inspector and run the program immediately. A debugger can
attach at any time. Default 127.0.0.1:9229.
|
--inspect-brk[=[host:]port] |
Start the inspector and execute nothing until a debugger attaches
and sends Runtime.runIfWaitingForDebugger, then break
on the first statement. Wins if both flags are given.
|
Both accept a bare port (--inspect=9230), which binds
127.0.0.1, or host:port. Anything else is
rejected before the program starts, with
oam run: invalid --inspect address '<value>' (expected [host:]port).
Executables built by oam compile accept the same two flags,
with one difference: they parse their own argv so that everything else
reaches your program, and there the value must be attached with
=. ./app --inspect-brk=9230 works;
./app --inspect-brk 9230 starts the inspector on the
default port and hands 9230 to your script.
oam test, oam check and oam repl
have no --inspect flag, and reject it rather than ignoring
it.
What it prints
Two lines on stderr, before your program produces anything:
Debugger listening on ws://127.0.0.1:9229/f314a409-397d-8046-f613-d63c27b9e2bd
For help, see: https://oamjs.org/docs/inspector
The first line is what tooling scrapes. The target id is fresh per process, so a wrapper script cannot cache the URL between runs — read it from stderr, or ask the discovery endpoint below.
Attaching
In Chrome, open chrome://inspect, add
127.0.0.1:9229 under "Discover network targets", and the
target appears as oam. The inspector also answers the
discovery endpoints a CDP client expects, on the same port:
$ curl -s http://127.0.0.1:9229/json/list
[{"description":"oam instance","id":"f314a409-...","title":"oam","type":"node",
"webSocketDebuggerUrl":"ws://127.0.0.1:9229/f314a409-...",
"devtoolsFrontendUrl":"devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:9229/f314a409-..."}]
$ curl -s http://127.0.0.1:9229/json/version
{"Browser":"oam/0.14.0","Protocol-Version":"1.3"}
/json and /json/list both return the single
target; there is always exactly one, because oam runs one context per
isolate.
One session at a time
A second client that tries to connect while another is attached is
turned away with 503 Service Unavailable rather than
silently sharing the session. Once the attached client disconnects the
next one is accepted, for the life of the process — so
reconnecting after closing DevTools works, but two debuggers at once do
not.
If a client disappears while execution is paused at a breakpoint, the pause is released and the program runs on to completion. A vanished debugger cannot leave a process wedged.
TypeScript: what the debugger actually sees
.ts file.
oam strips and transpiles TypeScript before V8 compiles it, and it does
not hand the debugger a source map. Your breakpoints land in generated
JavaScript.
The script V8 reports carries the path you ran — the
.ts file, not a temporary — but its
contents are the transpiled output, and
sourceMapURL is empty. For this input:
interface Point {
x: number;
y: number;
}
function dist(p: Point): number {
const total: number = p.x * p.x + p.y * p.y;
return Math.sqrt(total);
}
console.log(dist({ x: 3, y: 4 }));
a debugger asking for the script source gets:
function dist(p) {
const total = p.x * p.x + p.y * p.y;
return Math.sqrt(total);
}
console.log(dist({
x: 3,
y: 4
}));
Types are gone, and the code has been reflowed — the
console.log call is one line in the source and four in the
output. So line numbers do not agree between your editor and the
Sources panel, and a breakpoint set by line number in an external
client can land somewhere you did not mean. Set breakpoints in the
panel, on the code the panel is showing, or use a debugger;
statement, which survives the transpile and stops in the right place
every time.
Printed stack traces are a different story: those are mapped
back, because oam keeps its own source-map registry for them. The same
program throwing reports the .ts line you wrote:
Error: boom
at dist (C:\path\to\boom.ts:8:9)
at C:\path\to\boom.ts:12:13
The asymmetry is real and worth remembering: stack traces speak
TypeScript, the debugger speaks JavaScript. Plain
.js and .jsx entries are unaffected by any of
this; nothing rewrites a .js file's statements.
Exposing the port is exposing the process
The Chrome DevTools Protocol has no authentication. Anything that can
open that WebSocket can evaluate arbitrary code inside your process,
read every variable, and reach whatever the process can reach. The
default bind is 127.0.0.1 for that reason. Binding
0.0.0.0 on a shared or internet-facing host hands the
process to the network; if you need to debug remotely, forward the port
over SSH instead and leave the listener on loopback.
When it will not start
If the address is already in use, or the bind is refused, oam reports it as a diagnostic and exits rather than running your program without the debugger you asked for:
error[OAM-RT0004]: could not start inspector on 127.0.0.1:9229: <os error>
The usual cause is a previous --inspect run that has not
exited. Pick another port with --inspect=9230, or find the
process still holding 9229.
OAM-RT0004 is
overloaded: the same code also
means an unhandled promise rejection. Read the message, not the number.
An executable built by oam compile reports the same failure
without a code at all, as
could not start inspector: <os error>.