Edit, apply_patch, and user message summary were storing full before/after
file contents in metadata — causing sessions to balloon to 90+ MB and crash
the VS Code extension on load.
Strip before/after from edit filediff, apply_patch files[], and
summary.diffs both at write time (new data) and read time (existing data).
The subprocess restart approach did not solve the underlying Bun native
memory retention issue (oven-sh/bun#28318). Remove all restart plumbing
from rpc.ts, thread.ts, worker.ts, app.tsx, and sdk.tsx to keep the
diff clean. The diff-size and store-eviction fixes remain.
Bun Workers are threads within the same OS process — terminate() frees
the JSC context but mimalloc retains every page process-wide, so the
previous Worker-restart approach had zero effect on RSS.
Switch to Bun.spawn() with IPC which creates a separate child process.
Killing that process returns all its native memory to the OS.
The IPC relay pattern means the RPC client persists across subprocess
restarts — event listeners, fetch proxy, and SDK all continue working
without getter-indirection or rebinding.
Workaround for https://github.com/oven-sh/bun/issues/28318
Bun's JSC does not return freed native heap pages to the OS within a
single Worker lifetime. After large sessions, the only way to reclaim
that 2-3 GB of native allocator retention is to terminate the worker
and spawn a fresh one.
Workaround for https://github.com/oven-sh/bun/issues/28318
- Add getter-indirection layer so fetch/events transparently follow
worker replacement without rebuilding the TUI
- Add rejectAll() to RPC client to fail in-flight calls on termination
- Add rebindable event source that re-registers handlers on new client
- Wire /new command to fire-and-forget restart; sync layer re-bootstraps
via server.instance.disposed event from the new worker
- Guard against re-entry and skip in external server mode
User messages carry summary.diffs with full before/after file content
(the same giant strings as session_diff). The TUI never reads this
field. Strip it at both entry points (SSE handler + full sync) to
prevent multi-MB strings from accumulating in the Solid store.
The Solid store accumulated messages, parts, diffs, todos, status, and
permissions for every session visited during a TUI lifetime. Navigating
away via /new or the session list never freed the old session's data.
Add an evict() function that deletes all per-session entries from the
store maps and clears the fullSyncedSessions cache. Wire it into:
- A createEffect in app.tsx that fires when the route changes away
from a session (on() tracks prev vs current sessionID)
- The session.deleted SSE handler, which previously only removed the
session list entry but left orphaned per-session data
Existing sessions may have multi-GB before/after strings persisted in
session_diff JSON files. The read path in Summary.diff() now checks each
entry against the 256 KB cap and replaces oversized content with empty
strings, then rewrites the file so subsequent loads are fast. This follows
the existing unquoteGitPath migration pattern.
Avoids allocating multi-MB strings in the JS heap for oversized files.
Previously the full content was read then discarded; now the object size
is checked first via cat-file -s and the git show is skipped entirely
when either side exceeds 256 KB.
Defense-in-depth: destructure away before/after content from FileDiff
objects at both TUI store entry points (SSE handler + full sync). The
sidebar only reads file, additions, deletions — carrying full file content
in the Solid store is unnecessary and risks memory bloat.
When diffFull() reads file contents via git show, files exceeding 256 KB
(e.g. .heapsnapshot JSON) are now treated like binary files — before/after
are replaced with empty strings. This prevents multi-GB strings from
accumulating in downstream consumers (storage, SSE, TUI, VS Code, sharing).
Remove find (-exec), env (runs commands), git (hooks/aliases),
npm/yarn/pnpm/bun (postinstall scripts, run subcommand), and make
(executes Makefile recipes). Keep only commands that cannot spawn
subprocesses or execute arbitrary code.
- Remove commands that can execute arbitrary code (node, python, curl,
docker, etc.) from the default bash allowlist
- Keep only read-only/informational commands, text processing, file
operations, git, package managers, compilers, and archive tools
- Handle legacy TOML config file in bash migration to detect existing
users who only have the old config format
- Move bash permission migration from storage.ts to Config.global() so
it runs before config is consumed (fixes timing issue)
- Check all 5 global config files (config.json, kilo.json, kilo.jsonc,
opencode.json, opencode.jsonc) for existing bash permission before
migrating, and write to the highest-precedence existing file
- Add default allow-list of safe bash commands (ls, git, npm, etc.) so
users aren't prompted for common development commands
Add bash:ask to hardcoded permission defaults so new users are prompted
before shell commands execute. A storage migration preserves the existing
bash:allow behavior for existing users by writing it to their global config
if they haven't explicitly set a bash permission.
Instead of placing user before the orchestrator allowlist (which prevents
users from restricting allowed tools), keep user in the normal position
but add an explicit bash deny after user. This way:
- User can still deny orchestrator-allowed tools (e.g. webfetch)
- User cannot re-enable bash since the post-user deny wins via findLast