| | |

Biome 26 🧹 Debugging Configuration — Tracing and LSP logs

⏱️ Estimated read: 14 min | |

The Biome configuration is a declarative file, but the tool’s behavior depends on where the file is found, which overrides apply, and which version of the binary the editor is actually running. When the formatting does not apply, the rule does not fire, or the CLI and the editor disagree, the diagnosis is not the guess—it is the evidence. Biome provides the two evidence sources: the CLI tracing log, which records the timing and the internal span of the command, and the daemon log, which records the language server’s activity. The tracing is the mechanism that the performance problem is the measured, and the daemon log is the mechanism that the editor integration is the verified.

The tracing log is the CLI feature since Biome 2.0, and the combination of the --log-level=tracing, the --log-kind=json, and the --log-file=<path> is the mechanism that the full timing data is the captured . The output is the large JSON file, and the jq is the mechanism that the specific span is the filtered. The span names—the update_module_graph_internal, the pull_diagnostics, the format_file, and the open_file_internal—are the mechanisms that the specific phase is the inspected . The daemon log is the separate file, and the biome-logs directory is the mechanism that the LSP activity is the recorded.

Key point: The CLI tracing is the --log-level=tracing --log-kind=json --log-file=tracing.json combination. The jq filters the specific span by the name. The daemon log is in the biome-logs directory inside the Biome cache, and the BIOME_LOG_PATH is the override. The configuration resolution is the first check—the nearest biome.json from the working directory, then the upward traversal, then the home directory—and the editor’s biome.configurationPath is the override that the relative path may break since v2.4.0.


Why the tracing and the daemon log exist

The performance-blind problem. A Biome command that takes the ten seconds on the large project gives no clue about which phase is the slow. The tracing log records the span timing, and the span timing is the mechanism that the update_module_graph_internal or the pull_diagnostics is the identified . The blind is the reason the tracing exists, and the tracing is the mechanism that the performance problem is the data.

The editor-versus-CLI problem. The CLI formats the file correctly, and the editor does not. The two are the different binaries and the different configuration resolution. The daemon log records the LSP activity, and the log is the mechanism that the editor’s binary and the configuration path are the verified . The mismatch is the reason the daemon log exists, and the log is the mechanism that the integration is the debugged.

The config-resolution problem. The Biome looks for the nearest biome.json from the working directory, then the parent folders, then the home directory . The editor’s working directory is the project root, and the CLI’s is where the command is the executed. The two can be the different, and the different is the mechanism that the CLI finds the config and the editor does not—or the reverse . The resolution is the reason the config is the debugged, and the resolution is the mechanism that the biome.configurationPath is the override.

The relative-path problem. The Biome v2.4.0 introduced the regression: the biome.configurationPath with the relative path does not work in the VS Code, and the LSP server is unable to communicate with the editor . The workaround is the absolute path, and the absolute path is the mechanism that the regression is the avoided . The regression is the reason the configuration path is the debugged, and the path is the mechanism that the editor is the verified.

The plugin-shadowing problem. A nested biome.json shadows the root plugins even with the extends: "//", because the extends does not inherit the plugins . The plugin does not fire, and the reason is the configuration discovery. The root-level consolidation is the mechanism that the plugin fires everywhere, and the consolidation is the reason the plugin is the debugged .


a. The CLI tracing and the jq filtering

The CLI tracing is the three flags, and the flags are the requirement. The --log-level=tracing records the span timing, the --log-kind=json formats the output, and the --log-file=<path> writes the file.

# The tracing command
biome lint --log-level=tracing --log-kind=json --log-file=tracing.json

The --log-level=tracing is the mechanism that the span timing is the recorded . The --log-kind=json is the mechanism that the output is the JSON, and the JSON is the reason the jq can filter it. The --log-file=tracing.json is the mechanism that the log is the file rather than the stdout.

The jq filtering is the mechanism that the specific span is the extracted. The update_module_graph_internal is the module graph build, and the pull_diagnostics is the analysis.

# The slowest module graph file
cat tracing.json | jq 'select(.span.name == "update_module_graph_internal") | { path: .span.path, time_busy: .["time.busy"], time_idle: .["time.idle"] }' > filtered.json

# The slowest diagnostics file
cat tracing.json | jq '. | select(.span.name == "pull_diagnostics") | { path: .span.path, time_busy: .["time.busy"], time_idle: .["time.idle"] }' > filtered.json

The jq 'select(.span.name == "update_module_graph_internal")' is the mechanism that the specific span is the selected . The time_busy and the time_idle are the timing fields, and the path is the file path. The filtered.json is the mechanism that the result is the readable.

The format_file and the open_file_internal are the other spans. The format_file is the per-file format timing, and the open_file_internal is the file open and parse, with the reason field that tells whether the scanner, the watcher, or the client request is the cause .

# The slowest format file
cat tracing.json | jq 'select(.span.name == "format_file") | { path: .span.path, time_busy: .["time.busy"] }' > filtered.json

# The slowest open file
cat tracing.json | jq 'select(.span.name == "open_file_internal") | { path: .span.path, reason: .span.reason, time_busy: .["time.busy"] }' > filtered.json

The format_file is the mechanism that the slow file is the identified, and the open_file_internal is the mechanism that the slow parse is the identified. The reason is the mechanism that the cause is the known—the scanner is the initial scan, the watcher is the file change, and the client is the lint or the format.


b. The daemon log and the configuration resolution

The daemon log is the LSP server’s activity. The log is in the biome-logs directory inside the Biome cache, and the exact path is the biome __print_cache_dir command.

# The daemon log directory
biome __print_cache_dir
# The biome-logs directory is inside this path.

# The log directory override
BIOME_LOG_PATH="$PWD/.biome-logs" biome start

# The log prefix override
BIOME_LOG_PREFIX_NAME=my-project.log biome start

The biome __print_cache_dir is the mechanism that the cache path is the printed . The BIOME_LOG_PATH is the mechanism that the log directory is the override, and the override is the reason the project-specific log is the kept . The BIOME_LOG_PREFIX_NAME is the mechanism that the log file is the distinguished, and the distinguished is the reason the multiple projects are the separate.

The configuration resolution is the separate check. The Biome looks for the biome.json or the biome.jsonc or the .biome.json in the current working directory, then the parent folders, then the home directory . The editor’s working directory is the project root, and the CLI’s is the command’s directory.

# The configuration resolution
The current working directory → the biome.json
The parent folders → the biome.json
The home directory → the config
The default configuration → if the none found

The biome.configurationPath is the editor’s override. The relative path is the regression in the v2.4.0, and the absolute path is the workaround .

// .vscode/settings.json — the absolute path
{
  "biome.configurationPath": "/Users/you/project/biome.json"
}

The biome.configurationPath with the absolute path is the mechanism that the editor’s config is the resolved . The relative path is the mechanism that the regression is the triggered, and the absolute is the reason the editor is the working.

The biome.lsp.bin is the separate override. The binary path is the mechanism that the editor’s Biome version is the pinned, and the pin is the reason the editor and the CLI are the same version .

// .vscode/settings.json — the binary override
{
  "biome.lsp.bin": "/Users/you/project/node_modules/@biomejs/cli-darwin-arm64/biome"
}

The biome.lsp.bin is the mechanism that the editor’s binary is the explicit . The explicit is the reason the editor and the CLI are the consistent, and the consistency is the mechanism that the format-on-save is the same as the CLI.

The plugin shadowing is the separate check. The nested biome.json shadows the root plugins, and the extends: "//" does not inherit the plugins . The root-level consolidation is the mechanism that the plugin fires everywhere, and the consolidation is the reason the plugin is the working.

// biome.json — the root plugins
{
  "plugins": ["./rules/my-rule.grit"]
}

The root plugins is the mechanism that the plugin is the registered . The nested file that omits the plugins is the mechanism that the plugin does not fire, and the non-firing is the reason the plugin is the debugged. The biome check --formatter-enabled=false --assist-enabled=false is the mechanism that the plugin output is the isolated, and the isolation is the reason the plugin is the debugged .


Complete Example Session

# ============================================
# PART 1: THE CLI TRACING
# ============================================

biome lint --log-level=tracing --log-kind=json --log-file=tracing.json

# The tracing.json contains the span timing.

# ============================================
# PART 2: THE MODULE GRAPH FILTER
# ============================================

cat tracing.json | jq 'select(.span.name == "update_module_graph_internal") | { path: .span.path, time_busy: .["time.busy"], time_idle: .["time.idle"] }' > filtered.json

# The filtered.json has the slowest module graph files.

# ============================================
# PART 3: THE DIAGNOSTICS FILTER
# ============================================

cat tracing.json | jq '. | select(.span.name == "pull_diagnostics") | { path: .span.path, time_busy: .["time.busy"], time_idle: .["time.idle"] }' > filtered.json

# The filtered.json has the slowest analysis files.

# ============================================
# PART 4: THE FORMAT FILE FILTER
# ============================================

cat tracing.json | jq 'select(.span.name == "format_file") | { path: .span.path, time_busy: .["time.busy"] }' > filtered.json

# The filtered.json has the slowest format files.

# ============================================
# PART 5: THE OPEN FILE FILTER
# ============================================

cat tracing.json | jq 'select(.span.name == "open_file_internal") | { path: .span.path, reason: .span.reason, time_busy: .["time.busy"] }' > filtered.json

# The filtered.json has the slowest parse files with the reason.

# ============================================
# PART 6: THE DAEMON LOG DIRECTORY
# ============================================

biome __print_cache_dir

# The biome-logs directory is inside the printed path.

# ============================================
# PART 7: THE LOG OVERRIDE
# ============================================

BIOME_LOG_PATH="$PWD/.biome-logs" biome start

# The log directory is the project-specific.

# ============================================
# PART 8: THE CONFIGURATION RESOLUTION
# ============================================

# The current working directory → the biome.json
# The parent folders → the biome.json
# The home directory → the config
# The default configuration → if the none found

# ============================================
# PART 9: THE CONFIGURATION PATH
# ============================================

# .vscode/settings.json — the absolute path
{
  "biome.configurationPath": "/Users/you/project/biome.json"
}

# The relative path is the regression in the v2.4.0.

# ============================================
# PART 10: THE CHECKLIST
# ============================================

# [ ] The tracing flags are the three.
# [ ] The jq filters the specific span.
# [ ] The daemon log is in the biome-logs.
# [ ] The BIOME_LOG_PATH is the override.
# [ ] The configuration resolution is the first check.
# [ ] The configurationPath is the absolute path.

The ten parts covered the CLI tracing, the module graph filter, the diagnostics filter, the format file filter, the open file filter, the daemon log directory, the log override, the configuration resolution, the configuration path, and the checklist. The through-line is that the tracing is the performance evidence, and the configuration resolution is the first check.


Quick Reference

The tracing flags

FlagPurpose
--log-level=tracingThe span timing
--log-kind=jsonThe JSON output
--log-file=<path>The file output

The span names

SpanPurpose
update_module_graph_internalThe module graph build
pull_diagnosticsThe analysis
format_fileThe per-file format
open_file_internalThe open and parse, with the reason

The daemon log

AspectDetail
The directoryThe biome-logs inside the cache
The cache pathThe biome __print_cache_dir
The overrideThe BIOME_LOG_PATH
The prefixThe BIOME_LOG_PREFIX_NAME

The configuration resolution

OrderLocation
1The current working directory
2The parent folders
3The home directory
4The default configuration

Best Practices

✅ Do This:

# Use the three tracing flags
biome lint --log-level=tracing --log-kind=json --log-file=tracing.json // ✅

# Use the jq to filter the span
cat tracing.json | jq 'select(.span.name == "pull_diagnostics")' > filtered.json // ✅

# Use the absolute path for the configurationPath
"biome.configurationPath": "/Users/you/project/biome.json"         // ✅

# Use the BIOME_LOG_PATH for the project-specific log
BIOME_LOG_PATH="$PWD/.biome-logs" biome start                      // ✅

❌ Don’t Do This:

# Assume the tracing is the default
biome lint // the span timing is not the recorded                    // ❌

# Use the relative path for the configurationPath
"biome.configurationPath": "./biome.json" // the v2.4.0 regression // ❌

Common Pitfalls

PitfallWhy It HappensFix
The tracing is the hugeThe file is the largeUse the jq to filter
The daemon log is the missingThe log directory is the differentUse the biome __print_cache_dir
The editor ignores the configThe relative path is the regressionUse the absolute path
The plugin does not fireThe nested config shadows the rootConsolidate the root plugins

Real-World Examples

1. The tracing command

biome lint --log-level=tracing --log-kind=json --log-file=tracing.json

2. The module graph filter

cat tracing.json | jq 'select(.span.name == "update_module_graph_internal")'

3. The diagnostics filter

cat tracing.json | jq 'select(.span.name == "pull_diagnostics")'

4. The format file filter

cat tracing.json | jq 'select(.span.name == "format_file")'

5. The open file filter

cat tracing.json | jq 'select(.span.name == "open_file_internal")'

6. The daemon log directory

biome __print_cache_dir

7. The log override

BIOME_LOG_PATH="$PWD/.biome-logs" biome start

8. The absolute configuration path

{ "biome.configurationPath": "/Users/you/project/biome.json" }

9. The binary override

{ "biome.lsp.bin": "/Users/you/project/node_modules/@biomejs/cli-darwin-arm64/biome" }

10. The plugin isolation

biome check --formatter-enabled=false --assist-enabled=false file.ts

Visual

The Tracing Flow

┌──────────────────────────────────────────────────────────────┐
│  THE TRACING FLOW                                            │
│                                                              │
│  The Biome command                                           │
│         │                                                    │
│         ▼                                                    │
│  ┌─────────────────────┐                                    │
│  │ --log-level=tracing │  The span timing                   │
│  │ --log-kind=json     │  The JSON                          │
│  │ --log-file=path     │  The file                          │
│  └──────────┬──────────┘                                    │
│             │                                                │
│             ▼                                                    │
│  ┌─────────────────────┐                                    │
│  │ tracing.json        │  The large file                    │
│  └──────────┬──────────┘                                    │
│             │                                                │
│             ▼                                                    │
│  ┌─────────────────────┐                                    │
│  │ jq 'select(...)'    │  The filtered span                 │
│  └─────────────────────┘                                    │
└──────────────────────────────────────────────────────────────┘

The Configuration Resolution

┌──────────────────────────────────────────────────────────────┐
│  THE CONFIGURATION RESOLUTION                                │
│                                                              │
│  The current working directory                               │
│         │                                                    │
│         ▼                                                    │
│  ┌─────────────────────┐                                    │
│  │ The biome.json      │  The first found                   │
│  └──────────┬──────────┘                                    │
│             │                                                │
│             ▼                                                    │
│  ┌─────────────────────┐                                    │
│  │ The parent folders  │  The upward traversal              │
│  └──────────┬──────────┘                                    │
│             │                                                │
│             ▼                                                    │
│  ┌─────────────────────┐                                    │
│  │ The home directory  │  The last fallback                 │
│  └─────────────────────┘                                    │
└──────────────────────────────────────────────────────────────┘

The Editor Override

┌──────────────────────────────────────────────────────────────┐
│  THE EDITOR OVERRIDE                                         │
│                                                              │
│  ✅ The absolute path:                                        │
│  ┌─────────────────────┐                                    │
│  │ configurationPath:  │  The resolved                      │
│  │ /Users/you/...      │                                    │
│  └─────────────────────┘                                    │
│                                                              │
│  ❌ The relative path:                                        │
│  ┌─────────────────────┐                                    │
│  │ configurationPath:  │  The v2.4.0 regression             │
│  │ ./biome.json        │  The LSP fails                     │
│  └─────────────────────┘                                    │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
The tracing flagsThe --log-level=tracing, the --log-kind=json, the --log-file
The span namesThe update_module_graph_internal, the pull_diagnostics, the format_file, the open_file_internal
The daemon logThe biome-logs inside the cache
The log overrideThe BIOME_LOG_PATH
The configuration resolutionThe cwd → the parent → the home → the default
The editor pathThe absolute biome.configurationPath
The plugin shadowingThe nested config shadows the root

Key takeaways:

  • The CLI tracing is the performance evidence. The --log-level=tracing --log-kind=json --log-file=<path> combination records the span timing, and the jq filters the specific span. The update_module_graph_internal, the pull_diagnostics, the format_file, and the open_file_internal are the spans that the performance problem is the identified .
  • The daemon log is the editor evidence. The log is in the biome-logs directory inside the Biome cache, and the biome __print_cache_dir prints the exact path. The BIOME_LOG_PATH and the BIOME_LOG_PREFIX_NAME are the overrides that the project-specific log is the kept .
  • The configuration resolution is the first check. The Biome looks for the nearest biome.json from the working directory, then the parent folders, then the home directory . The CLI and the editor can have the different working directories, and the different is the reason the config is the debugged.
  • The biome.configurationPath with the relative path is the v2.4.0 regression. The absolute path is the workaround, and the workaround is the reason the editor is the working . The biome.lsp.bin is the separate binary override.
  • The nested biome.json shadows the root plugins. The extends: "//" does not inherit the plugins, and the plugin does not fire. The root-level consolidation is the mechanism that the plugin fires everywhere, and the --formatter-enabled=false --assist-enabled=false is the isolation that the plugin is the debugged .

Remember: The Biome debugging is the tracing and the configuration resolution. The tracing is the CLI performance evidence, and the daemon log is the editor evidence. The configuration resolution is the first check, and the editor’s absolute path is the workaround. The nested config shadows the root, and the root-level consolidation is the fix.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!