# Debugging and reload on save

> Reload on save and the r key, a debugger with --inspect-functions in Chrome or VS Code, and where to read each call's logs and errors.

Firenook reloads your functions when you save, can hold a call at a
breakpoint, and keeps every call's logs and errors. This page shows how to
use each while you work on a function.

## Reload on save

Firenook watches each codebase's folder. When you save a file, it waits
until a second passes with no further change, then loads that codebase
again in a new worker. The terminal says so:

```text
     19:47:46    ↻ reloaded        229 ms  3 functions · default
```

`default` is the codebase. The watcher leaves out files and folders whose
names start with a dot (so `.env` files too), `node_modules`, `venv`,
`*.log` files, and the patterns in the codebase's `ignore` list in
`firebase.json`.

A reload that fails keeps the version that loaded before, so your app keeps
working while you fix the error:

```text
     19:47:49    ✕ reload failed   121 ms  default
                 │ SyntaxError: Unexpected token ';'  functions/index.js:20
                 │ exports.oops = (;
                 │                 ^
                 │ still running the version loaded at 19:47:46 · open in console
```

Save the fix and the next reload takes over.

For TypeScript, Firenook runs the compiled JavaScript your `package.json`
points at. Start with `--build` and Firenook runs your compiler in watch mode
beside the emulators, so every save is compiled and reloaded;
[TypeScript functions](https://firenook.dev/docs/functions/typescript) has the details. If your
sources change but the compiled files do not, the console's page for the
codebase points it out.

### If a save seems to change nothing

In 0.2.0, a save that stops a codebase loading, such as code that throws
while it loads or a param with no default and no value, is reported only on
the terminal screen. The plain output (`--plain`, pipes and CI) and the
`--debug` log print nothing about it, and the version loaded before keeps
answering, so a call still gets the old behaviour. The screen shows it:

```text
     19:36:17    ✕ reload failed   117 ms  default
                 │ Functions codebase could not be analyzed successfully. It may have a syntax or runtime
                 │ error
                 │ Error: SHOP_REGION is not set
                 │ still running the version loaded at 19:35:59 · open in console
```

When you run with `--plain`, open the codebase in the console's Functions
page after a save: it says the codebase failed to load, with the error, and
that the previous version is still serving. Calling the function you changed
shows the same thing.

## Reload by hand

Press `r` in the terminal to reload every codebase of yours, or use
**Reload** on a codebase in the [console](https://firenook.dev/docs/tools/console). A reload by
hand is how a change to a `.env` file or `.secret.local` reaches your
functions, since Firenook does not reload on those
([environment variables and secrets](https://firenook.dev/docs/functions/environment)).

## Attach a debugger

Start with `--inspect-functions` to open Node's inspector on each codebase's
worker:

```sh
npx firenook emulators:start --inspect-functions
```

The first worker listens on `127.0.0.1:9229`, the next on 9230, and so on.
To choose the first port, give it: `--inspect-functions=9333`. With one
codebase, the plain log names its port:

```text
firenook functions: Using debug port 9229 for functions codebase default.
firenook functions: Debugger listening on ws://127.0.0.1:9229/27e0016a-a53d-4dc1-8fa2-1d8dbc90e235
```

Extensions take the first ports, then your codebases. So with an Extension
in `firebase.json`, the port you give goes to the Extension, and your code
gets the next one:

```text
firenook functions: Using debug port 9229 for functions codebase myext.
firenook functions: Using debug port 9230 for functions codebase default. You may need to add manually add this port to your inspector.
firenook functions: Debugger listening on ws://127.0.0.1:9230/fbe26b0c-0897-4cef-a4f0-de7742ee7ef8
firenook extensions[myext]: Debugger listening on ws://127.0.0.1:9229/0b80ac38-04f5-4411-a598-d0bba6e30439
```

Attach your debugger to the port the log gives `default` (or your
codebase's name), here 9230.

While the debugger is on, calls have no timeout, so a breakpoint can hold
one for as long as you need. Without it, a call stops at the function's
`timeoutSeconds`.

A reload starts a new worker, and with it a new debugger session on the same
port. After a reload, the new worker starts at the next call. Use a debugger
that attaches again by itself.

### Chrome DevTools

1. Start the emulators with `--inspect-functions`.

2. Open `chrome://inspect` in Chrome. Under **Devices**, choose
   **Configure** and check that `127.0.0.1:9229` (or your port) is in the
   list.

3. Choose **Open dedicated DevTools for Node**. That window attaches to the
   worker and attaches again after each reload.

4. In **Sources**, open your function's file, set a breakpoint, and call the
   function from your app or with `npx firenook functions:invoke`.

### VS Code

Add an attach configuration, start the emulators with
`--inspect-functions`, then run it from the Run and Debug view:

```json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "attach",
      "name": "Attach to Firenook functions",
      "address": "127.0.0.1",
      "port": 9229,
      "restart": true,
      "skipFiles": ["<node_internals>/**"]
    }
  ]
}
```

`"restart": true` makes VS Code attach again after every reload. With several
codebases, add one configuration per port.

## Function logs

### In the terminal

Each call is a line: the time, a tick or a cross, the function, how long it
took and what called it. The lines your function logged follow it, with
structured fields after the message:

```text
     19:47:43    ✓ helloWorld        7 ms  http · GET / · 200
                 │ Hello logs!   structuredData true
     19:47:43    ✕ brokenCheckout   11 ms  http · POST / · 500
                 │ checking out   order null
                 │ Unhandled error TypeError: Cannot read properties of null (reading 'items')
                 │   at functions/index.js:12:31
                 │ + 9 more lines · open in console
```

A failed call shows the error and the first line of the stack that is in
your own code. **open in console** leads to the whole call.

With `--plain`, or when the output goes to a pipe or CI, Firenook prints the
official emulator's lines instead (the stack trimmed):

```text
firenook functions: Beginning execution of "us-central1-brokenCheckout"
firenook functions: {"order":null,"severity":"INFO","message":"checking out"}
firenook functions: {"severity":"ERROR","message":"Unhandled error TypeError: Cannot read properties of null (reading 'items')\n    at ~/demo-shop/functions/index.js:12:31\n ..."}
firenook functions: Finished "us-central1-brokenCheckout" in 12ms
```

### In the console

**Functions › Invocations** in the console lists every call, live. Open one
to see how it ended and why, its timing, the request or event it got, the
response, its own log lines and the full stack of an error. **Replay** sends
the same request again to the code loaded now; **Edit and replay** lets you
change it first. Firenook keeps the last 2,000 calls.

### With --debug

`--debug` writes Firenook's whole log, with times, to
`.firenook/runs/session-*/firenook-debug.log`. In plain mode the start
prints the file's path:

```text
Debug log: ~/demo-shop/.firenook/runs/session-HRKIKS/firenook-debug.log
```

```text
[INFO] [2026-10-22T11:48:02.206861Z] [functions] functions: Beginning execution of "us-central1-brokenCheckout"
[INFO] [2026-10-22T11:48:02.216076Z] [functions] functions: {"order":null,"severity":"INFO","message":"checking out"}
[INFO] [2026-10-22T11:48:02.219083Z] [functions] functions: Finished "us-central1-brokenCheckout" in 12ms
```

Attach this file when you [report a problem](https://firenook.dev/docs/help/report).

## Read a failed call

Start with the terminal: the error's message and the line in your code that
threw. For the rest, open the call in the console. There you see the whole
stack, the request that caused it and the response your app got, and after a
fix you can replay the same request.

A call that runs past its `timeoutSeconds` is stopped, and the caller gets a
500:

```text
firenook functions[us-central1-quote]: Your function timed out after ~2s. To configure this timeout, see
      https://firebase.google.com/docs/functions/manage-functions#set_timeout_and_memory_allocation.
```

## Next

See everything the terminal shows in [the terminal](https://firenook.dev/docs/tools/terminal),
or trigger any function by hand with `functions:invoke`, described with
[Cloud Functions](https://firenook.dev/docs/services/functions).
