@code-yeongyu/senpi-codemode 2026.10.31-1 → 2026.10.31-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -1
  3. package/package.json +4 -4
  4. package/src/extension/host-facts.ts +81 -3
  5. package/src/extension/skill-contribution.ts +6 -6
  6. package/src/prompt/eval-prompt-template.ts +1 -1
  7. package/src/prompt/eval-prompt.ts +4 -2
  8. package/src/skill/bun/SKILL.md +53 -0
  9. package/src/skill/{bun-1-4 → bun}/references/breaking-changes.md +26 -4
  10. package/src/skill/{bun-1-4 → bun}/references/build.md +32 -6
  11. package/src/skill/bun/references/dev-tooling.md +67 -0
  12. package/src/skill/bun/references/http-networking.md +157 -0
  13. package/src/skill/bun/references/install.md +137 -0
  14. package/src/skill/{bun-1-4 → bun}/references/node-compat-platforms.md +16 -2
  15. package/src/skill/{bun-1-4 → bun}/references/performance.md +9 -3
  16. package/src/skill/{bun-1-4 → bun}/references/runtime-apis.md +38 -13
  17. package/src/skill/bun/references/security.md +74 -0
  18. package/src/skill/bun/references/session-recipes.md +71 -0
  19. package/src/skill/bun/references/test.md +132 -0
  20. package/src/tool/detached-cell-manager.ts +50 -3
  21. package/src/tool/eval-argument-validator.ts +59 -0
  22. package/src/tool/eval-request.ts +15 -9
  23. package/src/tool/eval-tool-options.ts +1 -1
  24. package/src/tool/eval-tool.ts +2 -0
  25. package/src/skill/bun-1-4/SKILL.md +0 -45
  26. package/src/skill/bun-1-4/references/dev-tooling.md +0 -34
  27. package/src/skill/bun-1-4/references/http-networking.md +0 -113
  28. package/src/skill/bun-1-4/references/install.md +0 -93
  29. package/src/skill/bun-1-4/references/security.md +0 -52
  30. package/src/skill/bun-1-4/references/test.md +0 -103
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [2026.10.31-2] - 2026-10-31
4
+
5
+ ### Breaking Changes
6
+
7
+ ### Added
8
+
9
+ - The eval prompt's `Host:` line honors the test-only `SENPI_TEST_WORKSTATION` pin (the same variable and shape coding-agent's workstation facts read, #4662): its CPU model and core count replace the real ones, so a harness driving the CLI gets the same prompt bytes on every host; unset or empty, nothing changes ([#4837](https://github.com/code-yeongyu/senpi/pull/4837))
10
+ - The same pin's optional `repoRoot: {from, to}` field shows the bun-1-4 skill path under the checkout `from` under the synthetic root `to` instead, so the checkout location does not reach the prompt either (#5067) ([#4837](https://github.com/code-yeongyu/senpi/pull/4837))
11
+
12
+ ### Changed
13
+
14
+ - The bundled `bun-1-4` skill is now `bun`: its capability map and operating rules cover Bun 1.4.1-1.4.4 (`bun check`, `bun fmt`, `Bun.FetchSession`, HTTP/2 in `Bun.serve`, offline installs) and the Bun.$ / Bun.spawn / bun test practices learned in agent sessions, with a new `references/session-recipes.md`; the eval prompt's pointer names the `bun` skill and also fires before a cell that spawns a process or runs tests ([#5158](https://github.com/code-yeongyu/senpi/pull/5158))
15
+
16
+ ### Fixed
17
+
18
+ - An eval call that leaves out `language`, `code` or `summary` now gets one clear error per missing field (naming the enabled languages) instead of a list of every schema branch it failed ([#5150](https://github.com/code-yeongyu/senpi/pull/5150))
19
+ - `eval` `peek`/`stop` with a mistyped `cell_id` now suggests the one close known cell id ([#5153](https://github.com/code-yeongyu/senpi/issues/5153))
20
+
21
+ ### Removed
22
+
3
23
  ## [2026.10.31-1] - 2026-10-31
4
24
 
5
25
  ### Breaking Changes
package/README.md CHANGED
@@ -42,7 +42,7 @@ task-tool names are known.
42
42
  JS runtime (`node`/`bun`).
43
43
  - JavaScript import rewriting for supported local modules and package imports
44
44
  in the persistent JS worker (Bun when senpi runs on bun, Node.js otherwise).
45
- - On a Bun >= 1.4 kernel the eval prompt names the bundled `bun-1-4` skill as
45
+ - On a Bun >= 1.4 kernel the eval prompt names the bundled `bun` skill as
46
46
  MUST READ before the first js cell; node kernels keep the Node.js wording.
47
47
  - GPT models receive a terse `eval` prompt dialect that prioritizes composing
48
48
  active tools through `tool.<name>(args)` and documents detach-on-timeout.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi-codemode",
3
- "version": "2026.10.31-1",
3
+ "version": "2026.10.31-2",
4
4
  "description": "Source-only senpi extension package for codemode evaluation tools",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -33,15 +33,15 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@babel/parser": "8.0.4",
36
- "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.31-1",
36
+ "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.31-2",
37
37
  "quickjs-wasi": "3.6.2",
38
38
  "typebox": "1.3.34"
39
39
  },
40
40
  "peerDependencies": {
41
- "@code-yeongyu/senpi": "2026.10.31-1"
41
+ "@code-yeongyu/senpi": "2026.10.31-2"
42
42
  },
43
43
  "devDependencies": {
44
- "@code-yeongyu/senpi": "2026.10.31-1"
44
+ "@code-yeongyu/senpi": "2026.10.31-2"
45
45
  },
46
46
  "keywords": [
47
47
  "senpi",
@@ -1,10 +1,88 @@
1
1
  import * as os from "node:os";
2
+ import * as path from "node:path";
2
3
  import { jsRuntimeLabel } from "./runtime-info.ts";
3
4
 
5
+ /**
6
+ * Test-only host pin (senpi#4660): the same `SENPI_TEST_WORKSTATION={"cpuModel":string,"cores":int,"kernel":string}`
7
+ * coding-agent's workstation.ts reads. Its CPU model and core count replace the real ones in the `Host:` line, so a
8
+ * harness driving the real CLI gets the same prompt bytes on every host. Its optional
9
+ * `"repoRoot":{"from":absolute path,"to":absolute path}` (senpi#5067) shows a path under the checkout `from` under `to`
10
+ * where the eval prompt renders it (`pinnedDisplayPath`). Unset (or empty) in every production launch.
11
+ */
12
+ const WORKSTATION_ENV = "SENPI_TEST_WORKSTATION";
13
+
14
+ interface PinnedWorkstation {
15
+ readonly cpuModel: string;
16
+ readonly cores: number;
17
+ readonly repoRoot?: { readonly from: string; readonly to: string };
18
+ }
19
+
20
+ function pinnedRepoRoot(value: unknown): { readonly from: string; readonly to: string } | undefined {
21
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
22
+ if (Object.keys(value).sort().join(",") !== "from,to" || !("from" in value) || !("to" in value)) return undefined;
23
+ const { from, to } = value;
24
+ if (typeof from !== "string" || typeof to !== "string" || !path.isAbsolute(from) || !path.isAbsolute(to))
25
+ return undefined;
26
+ return { from, to };
27
+ }
28
+
29
+ function pinnedWorkstation(env: NodeJS.ProcessEnv): PinnedWorkstation | undefined {
30
+ const value = env[WORKSTATION_ENV];
31
+ if (value === undefined || value === "") return undefined;
32
+ let parsed: unknown;
33
+ try {
34
+ parsed = JSON.parse(value);
35
+ } catch {
36
+ parsed = undefined;
37
+ }
38
+ const keys = typeof parsed === "object" && parsed !== null ? Object.keys(parsed).sort().join(",") : "";
39
+ if (
40
+ typeof parsed === "object" &&
41
+ parsed !== null &&
42
+ !Array.isArray(parsed) &&
43
+ (keys === "cores,cpuModel,kernel" || keys === "cores,cpuModel,kernel,repoRoot") &&
44
+ "cpuModel" in parsed &&
45
+ "cores" in parsed &&
46
+ "kernel" in parsed
47
+ ) {
48
+ const { cpuModel, cores, kernel } = parsed;
49
+ const repoRoot = "repoRoot" in parsed ? pinnedRepoRoot(parsed.repoRoot) : undefined;
50
+ if (
51
+ !("repoRoot" in parsed && repoRoot === undefined) &&
52
+ typeof cpuModel === "string" &&
53
+ cpuModel !== "" &&
54
+ typeof kernel === "string" &&
55
+ kernel !== "" &&
56
+ typeof cores === "number" &&
57
+ Number.isInteger(cores) &&
58
+ cores >= 1
59
+ )
60
+ return { cpuModel, cores, ...(repoRoot === undefined ? {} : { repoRoot }) };
61
+ }
62
+ throw new Error(
63
+ `${WORKSTATION_ENV} must be a JSON object {"cpuModel": non-empty string, "cores": positive integer, "kernel": non-empty string, optional "repoRoot": {"from": absolute path, "to": absolute path}}, got ${JSON.stringify(value)}`,
64
+ );
65
+ }
66
+
67
+ /**
68
+ * `filePath` as the eval prompt should show it: under the test-only `repoRoot` pin, a path inside the pinned checkout
69
+ * (`from`) is shown under `to`. Every other path, and every path when the pin is unset, is returned unchanged.
70
+ * Only for rendering: reads keep using the real path.
71
+ */
72
+ export function pinnedDisplayPath(filePath: string, env: NodeJS.ProcessEnv = process.env): string {
73
+ const root = pinnedWorkstation(env)?.repoRoot;
74
+ if (root === undefined) return filePath;
75
+ if (filePath === root.from) return root.to;
76
+ const prefix = root.from.endsWith(path.sep) ? root.from : root.from + path.sep;
77
+ return filePath.startsWith(prefix) ? path.join(root.to, filePath.slice(prefix.length)) : filePath;
78
+ }
79
+
4
80
  /** The one-line host description the eval prompt shows (platform, CPU, cores, JS runtime). */
5
- export function hostLine(): string {
6
- const cpu = os.cpus()[0]?.model?.trim();
7
- return [`${os.platform()} ${os.arch()}`, cpu, `${os.availableParallelism()} cores`, jsRuntimeLabel()]
81
+ export function hostLine(env: NodeJS.ProcessEnv = process.env): string {
82
+ const pinned = pinnedWorkstation(env);
83
+ const cpu = pinned ? pinned.cpuModel : os.cpus()[0]?.model?.trim();
84
+ const cores = pinned ? pinned.cores : os.availableParallelism();
85
+ return [`${os.platform()} ${os.arch()}`, cpu, `${cores} cores`, jsRuntimeLabel()]
8
86
  .filter((part): part is string => !!part)
9
87
  .join(" \u00b7 ");
10
88
  }
@@ -28,10 +28,10 @@ export function bunVersionSupportsSkill(version: string | undefined): boolean {
28
28
  return major > 1 || (major === 1 && minor >= 4);
29
29
  }
30
30
 
31
- const BUN_SKILL_PACKAGE_RELATIVE_PATH = join("skill", "bun-1-4", "SKILL.md");
31
+ const BUN_SKILL_PACKAGE_RELATIVE_PATH = join("skill", "bun", "SKILL.md");
32
32
 
33
33
  /**
34
- * Absolute path of the bundled bun-1-4 SKILL.md, or undefined (logged once) when it is not shipped.
34
+ * Absolute path of the bundled bun SKILL.md, or undefined (logged once) when it is not shipped.
35
35
  *
36
36
  * A compiled Bun binary has no readable module-relative asset, so resolution falls back to the
37
37
  * codemode sidecar shipped next to the executable, exactly as the Ruby and Julia kernel runners do.
@@ -41,7 +41,7 @@ export function bundledBunSkillPath(
41
41
  baseDir: string = BUN_SKILL_BASE_DIR,
42
42
  environment: CodemodeRuntimeAssetEnvironment = {},
43
43
  ): string | undefined {
44
- const localPath = join(baseDir, "..", "skill", "bun-1-4", "SKILL.md");
44
+ const localPath = join(baseDir, "..", "skill", "bun", "SKILL.md");
45
45
  const candidate = resolveCodemodeRuntimeAsset(localPath, BUN_SKILL_PACKAGE_RELATIVE_PATH, environment);
46
46
  if (existsSync(candidate)) return candidate;
47
47
  if (!loggedMissingBunSkill) {
@@ -58,14 +58,14 @@ export function bundledBunSkillPath(
58
58
  BUN_SKILL_PACKAGE_RELATIVE_PATH,
59
59
  );
60
60
  console.error(
61
- `[senpi-codemode] bundled bun-1-4 skill not found at ${localPath} or ${sidecarPath}; skipping contribution`,
61
+ `[senpi-codemode] bundled bun skill not found at ${localPath} or ${sidecarPath}; skipping contribution`,
62
62
  );
63
63
  }
64
64
  return undefined;
65
65
  }
66
66
 
67
67
  /**
68
- * Absolute path of the bundled bun-1-4 SKILL.md when it is active for this process:
68
+ * Absolute path of the bundled bun SKILL.md when it is active for this process:
69
69
  * the in-process js eval kernel itself runs bun >= 1.4 (`process.versions.bun`) and the
70
70
  * asset is shipped. A node kernel never activates it, regardless of any bun binary on PATH.
71
71
  */
@@ -77,7 +77,7 @@ export function activeBunSkillPath(
77
77
  return bundledBunSkillPath(baseDir);
78
78
  }
79
79
 
80
- /** Builds the `resources_discover` handler that contributes the active bun-1-4 skill, if any. */
80
+ /** Builds the `resources_discover` handler that contributes the active bun skill, if any. */
81
81
  export function createBunSkillDiscoverHandler(
82
82
  getKernelBunVersion: BunKernelVersionSource = kernelBunVersion,
83
83
  baseDir?: string,
@@ -29,7 +29,7 @@ Host: {{hostLine}}. Cells execute here. Size \`parallel(thunks)\` pools to its c
29
29
  A cell that outlives the foreground window detaches and keeps running; further cells on the same language queue behind it in submission order on the same kernel (state is shared, execution is serial) while other languages run at once, up to {{maxDetachedCells}} background cells per session; each detached cell completes as one notification. Do not re-run a detached or queued cell; read, cancel or enumerate them with \`eval({ action: "peek" | "stop", cell_id })\` / \`eval({ action: "list" })\`; list shows live and recently settled cells across languages without consuming notifications. Its own execution time is capped at {{runBudgetSeconds}}s (queue time and time inside host tool calls are not charged; the hard limit stays wall-clock from submission; raise \`timeout\` only for a declared long run), and a killed js cell that cannot settle restarts its kernel with every global lost.
30
30
 
31
31
  {{#if py}}Python runs on a live event loop: use top-level \`await\`; \`asyncio.run(…)\` raises.{{/if}}
32
- {{#if js}}{{#if jsBun}}JS runs in-process on Bun {{jsVersion}}: top-level \`await\`/\`return\` work; \`Bun.*\` builtins available, including \`new Bun.WebView()\`, a headless browser (navigate/click/evaluate/screenshot) to reach for before \`curl\` or a browser CLI when a page needs JS, a login, or a screenshot. Shell out through \`Bun.$\` or \`Bun.spawn\`, never \`Bun.spawnSync\`: a synchronous child blocks the worker and cannot be interrupted.{{#if bunSkillPath}} Before a cell that installs a package, spawns a server or PTY, or starts a long run, read the bun-1-4 skill at {{bunSkillPath}}; its builtins replace the npm packages you would otherwise install.{{/if}}{{else}}JS runs under Node.js worker: top-level \`await\`/\`return\` work; \`fetch\`/\`Buffer\` available.{{/if}}{{/if}}
32
+ {{#if js}}{{#if jsBun}}JS runs in-process on Bun {{jsVersion}}: top-level \`await\`/\`return\` work; \`Bun.*\` builtins available, including \`new Bun.WebView()\`, a headless browser (navigate/click/evaluate/screenshot) to reach for before \`curl\` or a browser CLI when a page needs JS, a login, or a screenshot. Shell out through \`Bun.$\` or \`Bun.spawn\`, never \`Bun.spawnSync\`: a synchronous child blocks the worker and cannot be interrupted.{{#if bunSkillPath}} Before a cell that installs a package, spawns a process, server or PTY, runs tests, or starts a long run, read the bun skill at {{bunSkillPath}}: its builtins replace the npm packages you would otherwise install, and it lists the Bun.$ / Bun.spawn / bun test traps that cost past sessions hours.{{/if}}{{else}}JS runs under Node.js worker: top-level \`await\`/\`return\` work; \`fetch\`/\`Buffer\` available.{{/if}}{{/if}}
33
33
  {{#if rb}}Ruby: synchronous; helper options are keyword args{{#if spawns}} (e.g. \`output("id", limit: 2)\`){{/if}}; the last expression auto-displays unless it is \`nil\`, an assignment, or a definition (like IRB).{{/if}}
34
34
  {{#if jl}}Julia: synchronous; helper options are standard keyword args{{#if spawns}} (e.g. \`output("id", limit=2)\`){{/if}}; the last expression auto-displays unless it is an assignment or a definition (like the Julia REPL).{{/if}}
35
35
  On error, fix and re-run only the failing step; a normal error keeps state, while a timeout or stop message says whether the kernel restarted.
@@ -1,5 +1,6 @@
1
1
  import type { KernelPreludeContribution } from "@code-yeongyu/senpi";
2
2
  import { DEFAULT_MAX_DETACHED_CELLS, DEFAULT_RUN_BUDGET_SECONDS } from "../config/settings.ts";
3
+ import { pinnedDisplayPath } from "../extension/host-facts.ts";
3
4
  import type { EvalRuntimeInfo } from "../tool/types.ts";
4
5
  import { EVAL_PROMPT_TEMPLATE } from "./eval-prompt-template.ts";
5
6
 
@@ -29,7 +30,7 @@ export interface EvalPromptOptions {
29
30
  readonly hostLine?: string;
30
31
  /** Identity of the in-process js kernel; a bun runtime swaps the Node.js worker line for the Bun one. */
31
32
  readonly jsRuntime?: EvalRuntimeInfo;
32
- /** Absolute path of the active bun-1-4 skill; rendered as a MUST READ pointer only on a bun kernel. */
33
+ /** Absolute path of the active bun skill; rendered as a MUST READ pointer only on a bun kernel. */
33
34
  readonly bunSkillPath?: string;
34
35
  /** Kill deadline for a cell's own execution time, as configured; the description states it. */
35
36
  readonly runBudgetSeconds?: number;
@@ -105,7 +106,8 @@ export function buildEvalPrompt(
105
106
  hostLine: options.hostLine ?? "",
106
107
  jsBun: options.jsRuntime?.name === "bun",
107
108
  jsVersion: options.jsRuntime?.version ?? "",
108
- bunSkillPath: options.bunSkillPath ?? "",
109
+ // pinnedDisplayPath: the test-only checkout pin (senpi#5067); the path itself unless SENPI_TEST_WORKSTATION pins it.
110
+ bunSkillPath: options.bunSkillPath === undefined ? "" : pinnedDisplayPath(options.bunSkillPath),
109
111
  runBudgetSeconds: String(options.runBudgetSeconds ?? DEFAULT_RUN_BUDGET_SECONDS),
110
112
  maxDetachedCells: String(options.maxDetachedCells ?? DEFAULT_MAX_DETACHED_CELLS),
111
113
  kernelPreludeDocs: (options.kernelPreludes ?? []).map((prelude) => prelude.documentation).join("\n"),
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: bun
3
+ description: "Read before a js eval cell that spawns a process, server or PTY, installs a package, or starts a long run, and before any bun -e, script, server, CLI, test, typecheck, format, bundle, or package-management work: this session's eval js kernel runs Bun 1.4+. Bun 1.4.x builtins replace 20+ npm deps and dev tools — check here BEFORE installing or running tsc/tsgo, prettier/oxfmt, sharp, puppeteer/playwright (scraping), cheerio, marked, node-cron, node-pty, execa/zx, concurrently, serve-static, tar, json5, fast-xml-parser, string-width, dotenv, keytar, aws-sdk (S3/R2) — and before writing Bun.$ / Bun.spawn / bun test code: the traps that cost past sessions hours are listed here. Triggers: eval js, bun, Bun.$, Bun.spawn, Bun.serve, bun test, bun check, bun fmt, bun build, bun install, image resize, headless browser, markdown, cron, PTY, secrets."
4
+ ---
5
+
6
+ # Bun 1.4.x — Use the Builtins First
7
+
8
+ Bun 1.4.0 (Aug 2026) through 1.4.4 (Oct 2026) ships builtins and CLIs that replace most utility npm packages and several dev tools. **Before adding a dependency, spawning a tool, or writing a workaround, check the map below — if Bun ships it, use the builtin.** Version tags (`[v1.4.3]`) in the references give exact minimums; dated notes `(2026-10-25)` mark facts measured in our own sessions. Release posts: <https://bun.com/blog/bun-v1.4> … <https://bun.com/blog/bun-v1.4.4>.
9
+
10
+ ## Capability map — what you can do now
11
+
12
+ | You want to... | Use | Replaces | Ref = `references/<name>.md` |
13
+ |---|---|---|---|
14
+ | Type-check a TS project: tsc-identical errors, 3-6x faster, no `typescript` install | `bun check --no-script`, `bun run/test/build --check` | tsc, tsgo | dev-tooling |
15
+ | Format with your Prettier/oxfmt config | `bun fmt --no-script` (`--check`, `--changed`, `--staged`) | prettier, oxfmt, lint-staged | dev-tooling |
16
+ | Run shell commands from JS, no `/bin/sh` | `` Bun.$`...` `` → `.nothrow().quiet().text()/.json()/.lines()` | execa, zx, shelljs | session-recipes |
17
+ | Spawn with timeout / PTY / IPC; measure a child | `Bun.spawn({ env: process.env, timeout, killSignal, terminal, ipc })`, `proc.resourceUsage()` | node-pty, tree-kill, pidusage | session-recipes |
18
+ | Keep tokens out of files, argv and env dumps | `Bun.secrets.get/set` (OS keychain), `--env-file=/dev/stdin` | keytar, dotenv | session-recipes |
19
+ | Resize/convert/rotate images | `Bun.file(p).image().resize().webp().write()` | sharp | runtime-apis |
20
+ | Headless browser: navigate, click, screenshot, evaluate, CDP | `new Bun.WebView()` | puppeteer, playwright (scraping) | runtime-apis |
21
+ | Scrape or rewrite HTML by CSS selector, streaming | `new HTMLRewriter().on("a[href]", { element })` | cheerio, jsdom (scraping) | runtime-apis |
22
+ | Markdown → HTML / React / ANSI | `Bun.markdown.html() / .react() / .ansi()` | marked, react-markdown, glow | runtime-apis |
23
+ | Schedule cron jobs | `Bun.cron()` | node-cron | runtime-apis |
24
+ | Run package scripts concurrently, glob-matched | `bun run --parallel "build:*"` | concurrently, npm-run-all | runtime-apis |
25
+ | Parse JSON5 / JSONL / JSONC / XML / TOML / YAML; tarballs; globs | `Bun.JSON5/.JSONL/.JSONC/.XML/.TOML/.YAML`, `Bun.Archive`, `Bun.Glob` | json5, ndjson, jsonc-parser, fast-xml-parser, @iarna/toml, js-yaml, tar, fast-glob | runtime-apis |
26
+ | ANSI-aware terminal text; table rendering | `Bun.stringWidth/sliceAnsi/wrapAnsi/stripANSI()`, `Bun.inspect.table()` | string-width, slice-ansi, wrap-ansi, strip-ansi, cli-table | runtime-apis |
27
+ | Hash, UUID v7, semver, deep-equal, gzip/zstd, which | `Bun.hash/CryptoHasher/password`, `randomUUIDv7`, `Bun.semver`, `deepEquals`, `gzipSync/zstdCompressSync`, `Bun.which` | uuid, semver, lodash.isequal, pako, which | runtime-apis |
28
+ | Postgres / MySQL / SQLite, Redis, S3-compatible (R2) clients | `Bun.SQL`, `bun:sqlite`, `Bun.RedisClient`, `Bun.S3Client` / `s3://` | pg, mysql2, better-sqlite3, ioredis, aws-sdk | runtime-apis |
29
+ | Serve a static dir (ETag/Range/304 handled) | `Bun.serve({ routes: { "/x/*": { dir } } })` | express.static, serve-static, sirv | http-networking |
30
+ | HTTP/2 + HTTP/3 server; fetch protocol/compress/proxy; per-client pools | `http2: true`, `http3: true`, `fetch(url, { protocol, compress, proxy })`, `new Bun.FetchSession()` | undici Agent | http-networking |
31
+ | Stream a download to disk; replay a request as curl | `Bun.write(path, await fetch(url))`, `BUN_CONFIG_VERBOSE_FETCH=curl` | pipeline + createWriteStream | http-networking |
32
+ | Tests across CPU workers / CI shards / changed files / flaky retry | `bun test --parallel --shard=1/3 --changed=main --retry` | jest/vitest infra | test |
33
+ | Kill "passes alone, fails in suite" bugs; hunt flakes | `bun test --isolate`, `--rerun-each N`, `--only-failures` | vitest default behavior | test |
34
+ | Single-file executables with embedded assets and profile-guided startup; feature flags; in-memory bundling; metafile; React Compiler | `bun build --compile --asset ./public --bytecode-order`, `bun:bundle feature()`, `files:{}`, `metafile: true`, `--react-compiler` | pkg hacks, esbuild define/plugins, babel-plugin-react-compiler | build |
35
+ | Diff package versions, fix vulns, dedupe/prune, licenses, explain a dep | `bun pm diff`, `bun audit fix`, `bun dedupe`, `bun prune`, `bun pm licenses`, `bun why` | npm-diff, npm audit fix, npm ls | install |
36
+ | Offline / cached installs; Docker layers; CI cache; 7x faster CI | `bun install --offline\|--prefer-offline`, `bun pm fetch`, `bun pm cache pack\|unpack`, `linker = "isolated"` | pnpm store | install |
37
+ | Profile CPU/heap/bundle as grep-able Markdown | `bun --cpu-prof-md`, `--heap-prof-md`, `bun build --metafile-md` | Chrome DevTools round-trip | dev-tooling |
38
+ | Run untrusted JS without eval / `new Function` / FFI | `--disallow-code-generation-from-strings=strict`, `--no-ffi-cc` | vm2 | security |
39
+ | Run Playwright, vitest, Next.js 16, OpenTelemetry, dd-trace; `undici`, `node-fetch`, `ws` | they now just work (the last three load Bun's builtin even when installed) | Node.js | node-compat-platforms |
40
+
41
+ Free wins (no code change): 2x lower CPU, 13-48% less server memory, 2-2.5x faster startup — numbers in [performance](references/performance.md).
42
+
43
+ ## Operating rules — 2 to 6 are what past sessions paid for
44
+
45
+ 1. **Eval-first, builtin-first.** The `eval` js kernel runs on Bun (that is why this skill is present): call `Bun.*` APIs directly inside js cells and reach for `bash` or a spawned `bun` only for CLI surfaces (`bun test`, `bun build`, `bun pm`) or work that needs a separate process. The Replaces column above is a ban list for new dependencies in production code, test helpers and one-off scripts alike.
46
+ 2. **Anything that must outlive a cell starts outside it.** The kernel SIGKILLs every `Bun.spawn` child when its cell settles, so browsers, daemons, dev servers, remote `ssh` jobs and slow git hooks start through `tool.monitor({ command })` or `tool.bash({ run_in_background: true })`; a stopped long `Bun.$` cell restarts the kernel and loses every global, so helpers live in an importable file.
47
+ 3. **`Bun.$` runs one command with array-interpolated args; real shell syntax goes to bash.** `${"a b"}` is ONE argument, `$?`/`$VAR` are literal, `for`/`until`/heredocs are parse errors or silent `command not found` with rc 0, and `VAR=x cmd` is not applied to a binary — each of these produced a false-green gate. Use `` $`cmd ${[args]}`.nothrow().quiet() `` for a command and `Bun.spawn(["bash", "-c", script], { env: process.env, stdin: "ignore" })` for a script.
48
+ 4. **Every `Bun.spawn` passes `env: process.env`, and re-spawns Bun as `process.execPath`.** An env-less child gets the environment the process STARTED with (runtime HOME/TMPDIR edits never arrive), and `"bun"` from PATH has resolved to three different shims on our machines. Give a child that reads stdin `stdin: "ignore"` or an explicit end, or it waits for EOF forever.
49
+ 5. **`bun test` gets its timeout on the command line and exact paths with `./`.** `--timeout N` is the only budget that reaches every file (a preload's `setDefaultTimeout` covers the first file, bunfig's is ignored); a timeout SIGTERMs every child the test spawned, so a database started in `beforeAll` dies with it; `bun test path.ts` without `./` is a substring filter. The full table is in [test](references/test.md) — read it before writing or debugging a suite.
50
+ 6. **Measure with the Bun the work targets.** The kernel's `Bun.version`, the shell's `bun --version` and a repo's pinned Bun (CI `bun-version`, `devDependencies.bun`) differ on one machine and 1.4.2 vs 1.4.3 changed test outcomes, so evidence runs use the pin; probe a feature in an empty directory, because `bun check`/`bun fmt`/`bun lint` otherwise run a same-named package.json script. Installs: `CI=1`, visible output, and after `--ignore-scripts` run `node node_modules/bun/install.js` — [install](references/install.md) lists why.
51
+ 7. **Read the matching reference before using an unfamiliar API** — signatures and caveats (`Bun.markdown` HTML is unsanitized, HTTP/3 is experimental, `writer({ append: true })` truncated a results file) live there, not here; [session-recipes](references/session-recipes.md) holds the rest of the session-learned traps.
52
+ 8. **Upgrading a project or debugging behavior that changed?** Read [breaking-changes](references/breaking-changes.md) first — 1.4.0 changed YAML/TOML strictness, `.xml`/`.css` imports, `Bun.$` globbing and fetch headers; 1.4.1-1.4.4 changed `Bun.Glob` parsing, WebSocket close timing and `bun install` flag handling.
53
+ 9. **TLS errors after upgrade are usually intentional** — 1.4.x tightened certificate verification across `fetch`, `tls.connect`, `Bun.connect`, `RedisClient`, `Bun.SQL`. See [security](references/security.md) before loosening anything.
@@ -1,6 +1,29 @@
1
- # Upgrading to Bun 1.4 — Behavior Changes
1
+ # Upgrading to Bun 1.4.x — Behavior Changes
2
2
 
3
- Blog: [#upgrading-to-1-4](https://bun.com/blog/bun-v1.4#upgrading-to-1-4). Most code is unaffected. Scan the Top 5, then the table for whatever subsystem you touched.
3
+ Blog: [#upgrading-to-1-4](https://bun.com/blog/bun-v1.4#upgrading-to-1-4), then the 1.4.1-1.4.4 posts. Most code is unaffected. Scan the Top 5, then the 1.4.1-1.4.4 table, then the 1.4.0 tables for whatever subsystem you touched.
4
+
5
+ ## 1.4.1 → 1.4.4 — changes that bit our repos
6
+
7
+ | Change | Version | Migration |
8
+ |---|---|---|
9
+ | `Bun.Glob`: `?( )`, `*( )`, `+( )`, `@( )`, `!( )` and `{1..3}` are extglob/ranges (were literal); applies to `--filter`, `workspaces`, `files`, `sideEffects` | 1.4.4 | a pattern with `*(1).txt` now means "zero or more 1"; escape as `*[(]1).txt`. An 11-group `{a,b}` allowlist that 1.4.3 refused matched on 1.4.4 — re-test auth boundaries after a Bun bump (2026-10-25) |
10
+ | `bun check` / `bun fmt` run a same-named package.json script first | 1.4.3 / 1.4.4 | `--no-script` for the builtin; probe features in an empty directory, since `bun lint --help` exits 0 via a script on a Bun with no linter (2026-10-22) |
11
+ | `bun install` rejects unknown flags (was: silently ran) | 1.4.4 | fix the typo; npm/Yarn/pnpm spellings are translated |
12
+ | Linux installs skip the other libc's native builds; `file:` folders are hardlinked (were per-file symlinks) | 1.4.4 | tools that filter optional deps by os/cpu must also filter `libc` (desktop stage-lock broke); "no links" checks see hardlinks |
13
+ | WebSocket client `close` waits for the closing handshake: `1005` not `1000` without a code, `1006` after 30 s; failed connects fire `error` then `close(1006)` | 1.4.4 | await the `close` event; `BUN_CONFIG_WS_CLOSE_TIMEOUT`; `ws.terminate()` to skip |
14
+ | `ws` is a port of ws 8.22: an `'error'` without a listener is an uncaught exception; `close` reason is a `Buffer`; no default idle timeout/ping on `ws` servers; `maxPayload` 100 MiB | 1.4.4 | add `'error'` listeners everywhere |
15
+ | Servers request a client certificate only with `requestCert: true` (`ca` alone no longer does) | 1.4.4 | set both for mTLS |
16
+ | `Bun.hash()`/`CryptoHasher`/`crypto.hash()` of a `Bun.file`/S3 file throw `TypeError` (was: hash of empty input) | 1.4.4 | read the bytes first |
17
+ | `Bun.SQL` ignores `PASSWORD`; `affectedRows` is a number on PG/SQLite (was `null`); `sql.array([...])` no longer casts to `JSON[]` | 1.4.4 | `PGPASSWORD` / URL / option |
18
+ | `fetch()` TLS name comes from the URL, not a custom `Host` header | 1.4.1 | `tls.servername` for IP + name |
19
+ | `fs.rmdir(p, { recursive: true })` works again (1.4.0 threw) | 1.4.1 | — |
20
+ | `--disallow-code-generation-from-strings=strict` also blocks plugins returning source, macros, and on-disk `.ts/.js` inside compiled binaries | 1.4.4 | return exports objects from plugins |
21
+ | Coverage counts skipped branches/`await` continuations/implicit constructors as uncovered | 1.4.4 | `% Lines`/`% Funcs` drop; re-check thresholds |
22
+ | PID 1 in a container exits `128 + signal` on Ctrl+C / `docker stop` | 1.4.4 | match on the new code |
23
+ | `bun add -E react@18` writes the installed exact version (was `18`) | 1.4.4 | — |
24
+ | `bun.lock` gains `hasInstallScript` / `libc`; older Bun drops them on save | 1.4.4 | pin one Bun per repo (CI `bun-version`) |
25
+ | Windows: `expect(p).rejects` after a socket close segfaulted the runner in 1.4.3 (upstream fix reverted) | fixed 1.4.4 | stay on 1.4.4+ on Windows CI |
26
+ | Still open on 1.4.4: `fs.rm` ignores `maxRetries`/`retryDelay` (bun#41480); `expect(p).rejects` after a Worker reply deadlocks (bun#37189) | — | explicit retry helper; capture rejections with `.then()` |
4
27
 
5
28
  ## Top 5 most likely to bite
6
29
 
@@ -52,7 +75,6 @@ Blog: [#upgrading-to-1-4](https://bun.com/blog/bun-v1.4#upgrading-to-1-4). Most
52
75
 
53
76
  | Change | Migration |
54
77
  |---|---|
55
- | `fs.rmdir` rejects `{ recursive: true }` | `fs.rm(path, { recursive: true, force: true })` |
56
78
  | `dns.lookup()` uses the system resolver (getaddrinfo) on Linux — split-DNS/VPN names now resolve | `Bun.dns.lookup(name, { backend: "c-ares" })` for old behavior |
57
79
  | fs/dns/pbkdf2 callback exceptions are `uncaughtException` (was unhandledRejection) | move the handler |
58
80
  | `dgram`: second `bind()` and post-`close()` calls throw synchronously | try/catch |
@@ -72,4 +94,4 @@ Blog: [#upgrading-to-1-4](https://bun.com/blog/bun-v1.4#upgrading-to-1-4). Most
72
94
  - `Temporal` defined by default (`BUN_JSC_useTemporal=0` to disable).
73
95
  - `bun.lock` is `lockfileVersion: 2`; nested/version-scoped overrides produce v3 (older Bun can't read v3).
74
96
  - `bun feedback` removed; `Bun.password.hash()` argon2 requires `memoryCost >= 8`.
75
- - Full exhaustive list: [Other behavior changes](https://bun.com/blog/bun-v1.4#other-behavior-changes) and the [Changelog](https://bun.com/blog/bun-v1.4#changelog).
97
+ - Full exhaustive lists: [1.4.0 Other behavior changes](https://bun.com/blog/bun-v1.4#other-behavior-changes) and the Bugfixes sections of the [1.4.1](https://bun.com/blog/bun-v1.4.1), [1.4.3](https://bun.com/blog/bun-v1.4.3) and [1.4.4](https://bun.com/blog/bun-v1.4.4) posts.
@@ -1,4 +1,12 @@
1
- # bun build / Bun.build() in 1.4
1
+ # bun build / Bun.build() in 1.4.x
2
+
3
+ ## Compiled-binary rules learned in production (omo ships one)
4
+
5
+ - **`bun` inside a compiled binary can be the binary itself.** Bun Shell resolves `bun` to `process.execPath` when PATH has no bun; in a `--compile` app that re-runs the app's own entry (argv `["bun", "/$bunfs/root/..."]`), which booted a phantom agent turn (omo#9362). `BUN_BE_BUN=1` makes a compiled executable behave as plain bun, but it propagates to grandchildren; senpi appends a real bun shim to PATH instead. `Bun.spawn(["bun"])` does not fall back (ENOENT).
6
+ - Worker threads do not see main-thread `process.env` writes — env for a kernel worker must travel explicitly.
7
+ - `bun:ffi` `cc()` cannot compile a `.c` file embedded in `$bunfs`; avoid runtime C compilation in shipped binaries (2026-10-26).
8
+ - `--compile` binaries ignore `NODE_COMPILE_CACHE` [v1.4.1] and no longer auto-load `tsconfig.json`/`package.json` from the cwd (`--compile-autoload-tsconfig` / `--compile-autoload-package-json` opt back in); `.env` and `bunfig.toml` still auto-load.
9
+ - `--compile-exec-argv="--disallow-code-generation-from-strings=strict"` bakes runtime flags into the binary; `BUN_OPTIONS` at launch can raise but not lower them.
2
10
 
3
11
  ## Built-in React Compiler [v1.4.0]
4
12
 
@@ -21,6 +29,15 @@ await Bun.build({ entrypoints: ["./src/index.tsx"], optimizeImports: ["antd", "@
21
29
 
22
30
  `import { Button } from "antd"` skips the hundreds of files behind names you didn't import. Automatic for packages with `"sideEffects": false`; opt others in with `optimizeImports`.
23
31
 
32
+ ## Smaller output since 1.4.1
33
+
34
+ - `export * as` groups (zod, Effect, fp-ts) tree-shake to the members you call: zod 4.5 375 KB → 77 KB minified. Namespace objects still accept assignment; `--no-deprecated-namespace-object-setters` makes them getter-only (future default).
35
+ - Dynamic `import()` and `require()` of an ES module tree-shake unused exports when every use reads a named export (`const { z } = await import("zod")` bundles like a static import).
36
+ - `--splitting`: fewer chunks (shared code stays in the entry), `--min-chunk-size=16384` folds tiny side-effect-free chunks, `<link rel="modulepreload">` + `__preload()` are emitted for browser builds (`--no-module-preload` to disable), bundle-wide names shrink minified output ~19%.
37
+ - `--splitting --target bun`: `require()` of an ES module becomes a lazy chunk (startup 23 ms → 11 ms on a 400-module CLI); `--no-split-require` keeps the old inlining.
38
+ - `[hash9]`…`[hash13]` in `naming` widens chunk hashes; two chunks can no longer collide on an 8-char hash [v1.4.3]. `metafile.inputs[*].entryPoint` names the file behind a split `import()` [v1.4.3].
39
+ - `Bun.build({ check: true })` / `bun build --check` type-checks first and writes nothing on a type error [v1.4.3].
40
+
24
41
  ## Compile-time feature flags: bun:bundle [v1.3.5]
25
42
 
26
43
  Blog: [#compile-time-feature-flags-with-bun-bundle](https://bun.com/blog/bun-v1.4#compile-time-feature-flags-with-bun-bundle)
@@ -72,11 +89,20 @@ bun build ./build/index.js --compile \
72
89
 
73
90
  Keeps original filenames; `path.join(import.meta.dir, ...)` works. `node:fs` treats `/$bunfs/` as a real tree (`existsSync`, `readdirSync` recursive/withFileTypes, ...), so static-file servers run unmodified inside the binary.
74
91
 
75
- ## Bytecode for ES modules [v1.3.9]
92
+ ## Bytecode for ES modules [v1.3.9], smaller and PGO-ordered [v1.4.1, v1.4.3]
76
93
 
77
- Blog: [#bytecode-compilation-for-es-modules](https://bun.com/blog/bun-v1.4#bytecode-compilation-for-es-modules)
94
+ Blog: [#bytecode-compilation-for-es-modules](https://bun.com/blog/bun-v1.4#bytecode-compilation-for-es-modules), [1.4.3 #profile-guided-bytecode-layout](https://bun.com/blog/bun-v1.4.3)
95
+
96
+ `--bytecode --format=esm` (requires `--compile`) enables top-level await, `import.meta`, dynamic imports, and code splitting in bytecode-compiled binaries (previously CJS-only). Bytecode is now ~3x the source size instead of 9x (Claude Code's install: 376 MB → 207 MB) and the cache format is identical on every platform, so cross-compiling with `--bytecode` works and builds are byte-identical [v1.4.1].
97
+
98
+ ```bash
99
+ bun build --compile --bytecode ./cli.ts --outfile myapp
100
+ BUN_BYTECODE_ORDER_OUT=myapp.order ./myapp --help # run it the way users do; the profile is written on exit
101
+ bun build --compile --bytecode --bytecode-order=myapp.order ./cli.ts --outfile myapp # cold start 1.01 s → 0.53 s on a large CLI
102
+ ```
78
103
 
79
- `--bytecode --format=esm` (requires `--compile`) enables top-level await, `import.meta`, dynamic imports, and code splitting in bytecode-compiled binaries (previously CJS-only).
104
+ - `--bytecode-depth=1` limits ahead-of-time bytecode to shallow functions (rest compile on first call); `--compile-jit-policy <n>` keeps run-once startup code in the interpreter, then `Bun.unsafe.setJITPolicy(1)` once interactive; `--no-optimize-bytecode` skips the optimizer. `bytecodeOrderStats()` from `bun:jsc` reports profile coverage.
105
+ - Text imports (`with { type: "text" }`) are embedded once, pre-encoded: a 3 MB file adds 3 MB to the binary and ~1 MB of memory instead of 6 MB / 12.6 MB [v1.4.1].
80
106
 
81
107
  ## metafile: true and --metafile-md [v1.3.6 / v1.3.8]
82
108
 
@@ -109,6 +135,6 @@ Active when `experimentalDecorators` is off in tsconfig. Classes, methods, field
109
135
 
110
136
  Reachability walk is now BFS O(V+E); a 20,000-module DAG links in 320ms (was 4.65s). Tree-shaking/TLA/CSS-order passes use explicit stacks, so linear chains of thousands of modules link without stack growth.
111
137
 
112
- ## Compile gotcha (v1.3.4 change)
138
+ ## Memory and speed [v1.4.1, v1.4.3]
113
139
 
114
- `bun build --compile` binaries no longer auto-load `tsconfig.json` / `package.json` from the runtime cwd. Opt back in with `--compile-autoload-tsconfig` / `--compile-autoload-package-json`. `.env` and `bunfig.toml` still auto-load.
140
+ Bundling three.js x100: 1.83 s → 1.12 s. 800 entry points with `--splitting`: peak RSS 2,121 MB → 276 MB [v1.4.3]. Nested classes and functions keep their names (`Model`, not `Model2`) [v1.4.1].
@@ -0,0 +1,67 @@
1
+ # Bun 1.4.x Dev Tooling & Observability
2
+
3
+ Blog: [#dev-tooling](https://bun.com/blog/bun-v1.4#dev-tooling), [#observability](https://bun.com/blog/bun-v1.4#observability), [1.4.3 bun check](https://bun.com/blog/bun-v1.4.3), [1.4.4 bun fmt](https://bun.com/blog/bun-v1.4.4)
4
+
5
+ ## bun check — TypeScript type checker (replaces tsc / tsgo) [v1.4.3, 1.4-7.8x faster in v1.4.4]
6
+
7
+ Docs: <https://bun.com/docs/runtime/check>
8
+
9
+ ```bash
10
+ bun check --no-script # whole project from the nearest tsconfig.json; every core; no `typescript` install needed
11
+ bun check --no-script src/index.ts # one file and everything it imports
12
+ bun check --no-script -b # follow project references, like tsc -b (shares caches across projects)
13
+ bun check --no-script --no-pretty # one line per error, tsc --pretty false shape (default when piped)
14
+ bun run --check src/index.ts # type-check, then run (also package.json scripts and --watch)
15
+ bun test --check / bun build --check # no tests run / nothing is written on a type error; Bun.build({ check: true })
16
+ ```
17
+
18
+ - **`bun check` runs the package.json `check` script when one exists** (senpi, omo and the desktop repo all have one), and `bun fmt` likewise runs a `fmt` script. Measured on 1.4.4: `bun check` in such a project printed the script's output and exited 0 with a type error present. `--no-script` reaches the builtin.
19
+ - Port of typescript-go: same errors, same codes, same positions as `tsc` 7.0.2 (51,210/51,210 conformance baselines; 0 mismatches across 72 misconfigured OSS repos). A difference from `tsc` is a Bun bug.
20
+ - Type-check only: no emit, no `.d.ts`, no language server — LSP diagnostics still come from the editor's TypeScript.
21
+ - Any compiler option works as a flag and overrides tsconfig: `bun check --noUncheckedIndexedAccess`.
22
+
23
+ ## bun fmt — Prettier-compatible formatter (replaces prettier, oxfmt, lint-staged) [v1.4.4]
24
+
25
+ Docs: <https://bun.com/docs/format>
26
+
27
+ ```bash
28
+ bun fmt --no-script # format the project; reads .prettierrc*, prettier.config.*, .editorconfig, .prettierignore, .oxfmtrc*
29
+ bun fmt --no-script --check # list files that need formatting; exit 1 if any
30
+ bun fmt --no-script --changed[=origin/dev] # only files that differ from the default branch (or a ref)
31
+ bun fmt --no-script --staged # pre-commit: format staged files and re-stage them
32
+ bun fmt --doctor # how Bun reads your config; which prettier plugins/packages you can uninstall
33
+ ```
34
+
35
+ - JS/TS/JSX, CSS/SCSS/Less, HTML/Vue/Svelte/Angular, JSON/JSONC/JSON5, YAML, TOML, Markdown/MDX, GraphQL, Handlebars, plus embedded code. 99.1% byte-identical to Prettier 3.9.9 on its own test suite; 286x faster.
36
+ - Built in: import sorting, Tailwind class sorting (uses the project's `tailwindcss`), `package.json` key sorting, Svelte. Unsupported plugins (e.g. `prettier-plugin-jsdoc`) print an error, skip their files, exit 2; Astro-style plugin languages fall back to the project's Prettier.
37
+ - `[fmt]` in bunfig.toml takes every Prettier option by the same name (`bun fmt --init`); `--debug-check` fails if formatting changed a file's meaning.
38
+
39
+ ## Markdown profilers — built for terminals and LLMs
40
+
41
+ ```bash
42
+ bun --cpu-prof-md ./app.ts # CPU profile as Markdown: hot functions by self time,
43
+ # call tree, who-calls-whom. grep it, paste into a bug or an LLM.
44
+ bun --heap-prof-md ./app.ts # heap profile as Markdown: total size, types by retained size,
45
+ # largest objects, retention chains. Includes grep recipes in the header.
46
+ bun build ./src/index.ts --outdir ./dist --metafile-md=./dist/meta.md # bundle-size analysis as Markdown
47
+ ```
48
+
49
+ - `bun --cpu-prof` / `--heap-prof` still write `.cpuprofile` / V8-compatible `.heapsnapshot` for Chrome DevTools / VS Code.
50
+ - `BUN_CPU_PROFILE=1` turns on the CPU profiler for a process you cannot pass flags to (e.g. a framework-spawned worker).
51
+ - **Agent workflow: when a Bun process is slow or leaking, run the `-md` variants and read the report directly — no DevTools round-trip.**
52
+
53
+ ## Async stack traces [v1.4.0]
54
+
55
+ Errors from async native APIs (`fs.promises`, `Bun.file()`, S3, DNS, crypto, `fetch`) point at the `await` in your code, not native frames.
56
+
57
+ Process lifetime (`--no-orphans`), env files, `BUN_OPTIONS`, memory measurement and `BUN_CONFIG_VERBOSE_FETCH=curl` are in [session-recipes](session-recipes.md).
58
+
59
+ ## Watch and hot reload
60
+
61
+ `bun --watch app.ts` restarts the process on file change (`--watch-kill-signal=SIGTERM` picks the signal, `--no-clear-screen` keeps scrollback); `bun --hot app.ts` swaps modules in place without restarting — `Bun.serve` keeps its port, `Bun.SQL` keeps its connections [v1.4.4], and `import.meta.hot.data` carries state across reloads. `bun test --watch` re-runs affected files.
62
+
63
+ ## APM / tracing works now
64
+
65
+ - **`node:inspector`**: a `Session` can start/stop CPU profiles while the app runs (`Profiler.start`/`Profiler.stop`).
66
+ - **Datadog**: `dd-trace` traces requests; `@datadog/pprof` profiles continuously (required V8 C++ APIs implemented).
67
+ - **OpenTelemetry**: `@opentelemetry/instrumentation-http` and `-fs` export spans; `shimmer` and `require-in-the-middle` can patch bundled code.