Skip to content

fix/memory 20695#5

Merged
coleleavitt merged 1036 commits intodevfrom
fix/memory-20695
Apr 21, 2026
Merged

fix/memory 20695#5
coleleavitt merged 1036 commits intodevfrom
fix/memory-20695

Conversation

@coleleavitt
Copy link
Copy Markdown
Owner

@coleleavitt coleleavitt commented Apr 21, 2026


Summary by cubic

Fix critical memory leaks and add automatic heap snapshots to keep opencode, the CLI, and the TUI/server stable under heavy load. Addresses Linear 20695 by measuring RSS correctly on Linux and reducing session memory pressure.

  • Bug Fixes

    • Accurate Linux RSS measurement for auto heap snapshot thresholds.
    • Stop writing/reading the summary_diffs column to eliminate RSS bloat.
    • Fix TypeScript LSP leak and a pty session handle leak; restrict LSP servers to the current instance/cwd.
    • Reduce snapshot overhead by skipping typechecking and ignoring generated model snapshot files and files >2MB.
    • Preserve dev.log across restarts for crash forensics.
  • Performance

    • Automatic heap snapshots for high-memory CLI, TUI, and server processes.
    • Batch diffFull snapshot blob reads to reduce memory spikes.

Written for commit 354ef97. Summary will update on new commits.

kitlangton and others added 30 commits March 24, 2026 19:11
The stale-issues workflow was hitting the default 30 operations limit,
preventing it from processing all 2900+ issues/PRs. Increased to 1000
to handle the full backlog. Also pinned to exact v10.2.0 for reproducibility.
- Create script/github/close-issues.ts to close stale issues after 60 days
- Add GitHub Action workflow to run daily at 2 AM
- Remove old stale-issues workflow to avoid conflicts
- Add contents: read permission for checkout
- Use github.token instead of secrets.GITHUB_TOKEN
coleleavitt and others added 8 commits April 19, 2026 19:00
…ls (anomalyco#16952)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Nate Williams <50088025+natewill@users.noreply.github.com>
…lyco#20695)

process.memoryUsage().rss underreports ~60x in Bun on Linux because
bmalloc mmap regions aren't counted. Read /proc/self/statm instead so
the 2 GB snapshot threshold actually fires. Falls back to process.memoryUsage().rss on non-Linux.

Per sjawhar's megathread analysis:
anomalyco#20695
…S bloat (anomalyco#20695)

FileDiff.before/after store full file contents as strings. Persisting
them in the summary_diffs JSON column caused in-memory retention on every
Session.get(). sjawhar measured 22 copies of one 20 MB JSON file = 440 MB
from a single file.

Full diffs still persist to session_diff file storage via
storage.write([session_diff, sessionID], diffs) and are retrievable on
demand via Session.diff(sessionID). The column is kept in the schema
for backwards compatibility but we now always write null.

Message-level message.summary.diffs (different code path) untouched;
UI consumers (session-turn, message-nav, session.tsx) use that path.

Ref: anomalyco#20695
Previously Log.init() called fs.truncate() on dev.log at every startup,
destroying the prior session's logs. When opencode crashed with a bun
int3 trap, restart wiped the evidence within seconds.

Now in dev mode we rotate dev.log to dev.log.<timestamp> instead. Cleanup
trims rotated files to the most recent 10 (matching the existing policy
for timestamped production logs).

Related: bun allocator assertion crashes in anomalyco#20695 megathread.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

Important

Review skipped

Too many files!

This PR contains 296 files, which is 146 over the limit of 150.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0a75942e-dc4f-4a10-863e-8530ba0f4ae9

📥 Commits

Reviewing files that changed from the base of the PR and between 4205fbd and 354ef97.

⛔ Files ignored due to path filters (4)
  • bun.lock is excluded by !**/*.lock
  • flake.lock is excluded by !**/*.lock
  • packages/console/app/src/asset/go-ornate-dark.svg is excluded by !**/*.svg
  • packages/console/app/src/asset/go-ornate-light.svg is excluded by !**/*.svg
📒 Files selected for processing (296)
  • .github/VOUCHED.td
  • .github/actions/setup-bun/action.yml
  • .github/workflows/close-issues.yml
  • .github/workflows/deploy.yml
  • .github/workflows/docs-locale-sync.yml
  • .github/workflows/nix-hashes.yml
  • .github/workflows/publish.yml
  • .github/workflows/sign-cli.yml
  • .github/workflows/stale-issues.yml
  • .github/workflows/storybook.yml
  • .github/workflows/test.yml
  • .github/workflows/vouch-manage-by-issue.yml
  • .gitignore
  • .opencode/.gitignore
  • .opencode/agent/docs.md
  • .opencode/agent/translator.md
  • .opencode/command/changelog.md
  • .opencode/glossary/tr.md
  • .opencode/opencode.jsonc
  • .opencode/plugins/smoke-theme.json
  • .opencode/plugins/tui-smoke.tsx
  • .opencode/themes/.gitignore
  • .opencode/tool/github-pr-search.ts
  • .opencode/tool/github-pr-search.txt
  • .opencode/tool/github-triage.ts
  • .opencode/tool/github-triage.txt
  • .opencode/tui.json
  • .signpath/policies/opencode/test-signing.yml
  • AGENTS.md
  • README.ar.md
  • README.bn.md
  • README.br.md
  • README.bs.md
  • README.da.md
  • README.de.md
  • README.es.md
  • README.fr.md
  • README.gr.md
  • README.it.md
  • README.ja.md
  • README.ko.md
  • README.md
  • README.no.md
  • README.pl.md
  • README.ru.md
  • README.th.md
  • README.tr.md
  • README.uk.md
  • README.vi.md
  • README.zh.md
  • README.zht.md
  • github/index.ts
  • infra/console.ts
  • nix/hashes.json
  • nix/node_modules.nix
  • nix/opencode.nix
  • package.json
  • packages/app/create-effect-simplification-spec.md
  • packages/app/e2e/AGENTS.md
  • packages/app/e2e/actions.ts
  • packages/app/e2e/app/home.spec.ts
  • packages/app/e2e/app/palette.spec.ts
  • packages/app/e2e/app/server-default.spec.ts
  • packages/app/e2e/app/titlebar-history.spec.ts
  • packages/app/e2e/commands/panels.spec.ts
  • packages/app/e2e/files/file-tree.spec.ts
  • packages/app/e2e/files/file-viewer.spec.ts
  • packages/app/e2e/fixtures.ts
  • packages/app/e2e/projects/project-edit.spec.ts
  • packages/app/e2e/projects/projects-close.spec.ts
  • packages/app/e2e/projects/projects-switch.spec.ts
  • packages/app/e2e/projects/workspace-new-session.spec.ts
  • packages/app/e2e/projects/workspaces.spec.ts
  • packages/app/e2e/prompt/prompt-async.spec.ts
  • packages/app/e2e/prompt/prompt-history.spec.ts
  • packages/app/e2e/prompt/prompt-multiline.spec.ts
  • packages/app/e2e/prompt/prompt-shell.spec.ts
  • packages/app/e2e/prompt/prompt-slash-share.spec.ts
  • packages/app/e2e/prompt/prompt-slash-terminal.spec.ts
  • packages/app/e2e/prompt/prompt.spec.ts
  • packages/app/e2e/selectors.ts
  • packages/app/e2e/session/session-child-navigation.spec.ts
  • packages/app/e2e/session/session-composer-dock.spec.ts
  • packages/app/e2e/session/session-model-persistence.spec.ts
  • packages/app/e2e/session/session-review.spec.ts
  • packages/app/e2e/session/session-undo-redo.spec.ts
  • packages/app/e2e/settings/settings-keybinds.spec.ts
  • packages/app/e2e/settings/settings.spec.ts
  • packages/app/e2e/sidebar/sidebar-popover-actions.spec.ts
  • packages/app/e2e/sidebar/sidebar-session-links.spec.ts
  • packages/app/e2e/sidebar/sidebar.spec.ts
  • packages/app/e2e/terminal/terminal-init.spec.ts
  • packages/app/e2e/terminal/terminal-reconnect.spec.ts
  • packages/app/e2e/terminal/terminal-tabs.spec.ts
  • packages/app/e2e/terminal/terminal.spec.ts
  • packages/app/e2e/tsconfig.json
  • packages/app/e2e/utils.ts
  • packages/app/index.html
  • packages/app/package.json
  • packages/app/playwright.config.ts
  • packages/app/public/oc-theme-preload.js
  • packages/app/script/e2e-local.ts
  • packages/app/src/app.tsx
  • packages/app/src/components/debug-bar.tsx
  • packages/app/src/components/dialog-connect-provider.tsx
  • packages/app/src/components/dialog-custom-provider-form.ts
  • packages/app/src/components/dialog-custom-provider.test.ts
  • packages/app/src/components/dialog-custom-provider.tsx
  • packages/app/src/components/dialog-edit-project.tsx
  • packages/app/src/components/dialog-fork.tsx
  • packages/app/src/components/dialog-release-notes.tsx
  • packages/app/src/components/dialog-select-directory.tsx
  • packages/app/src/components/dialog-select-file.tsx
  • packages/app/src/components/dialog-select-mcp.tsx
  • packages/app/src/components/dialog-select-model-unpaid.tsx
  • packages/app/src/components/dialog-select-model.tsx
  • packages/app/src/components/dialog-select-provider.tsx
  • packages/app/src/components/dialog-select-server.tsx
  • packages/app/src/components/file-tree.tsx
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/attachments.test.ts
  • packages/app/src/components/prompt-input/attachments.ts
  • packages/app/src/components/prompt-input/build-request-parts.test.ts
  • packages/app/src/components/prompt-input/build-request-parts.ts
  • packages/app/src/components/prompt-input/files.ts
  • packages/app/src/components/prompt-input/history.test.ts
  • packages/app/src/components/prompt-input/history.ts
  • packages/app/src/components/prompt-input/paste.ts
  • packages/app/src/components/prompt-input/submit.test.ts
  • packages/app/src/components/prompt-input/submit.ts
  • packages/app/src/components/server/server-row.tsx
  • packages/app/src/components/session-context-usage.tsx
  • packages/app/src/components/session/session-context-tab.tsx
  • packages/app/src/components/session/session-header.tsx
  • packages/app/src/components/session/session-new-view.tsx
  • packages/app/src/components/session/session-sortable-terminal-tab.tsx
  • packages/app/src/components/settings-agents.tsx
  • packages/app/src/components/settings-commands.tsx
  • packages/app/src/components/settings-general.tsx
  • packages/app/src/components/settings-keybinds.tsx
  • packages/app/src/components/settings-list.tsx
  • packages/app/src/components/settings-mcp.tsx
  • packages/app/src/components/settings-models.tsx
  • packages/app/src/components/settings-permissions.tsx
  • packages/app/src/components/settings-providers.tsx
  • packages/app/src/components/status-popover-body.tsx
  • packages/app/src/components/status-popover.tsx
  • packages/app/src/components/terminal.tsx
  • packages/app/src/components/titlebar.tsx
  • packages/app/src/constants/file-picker.ts
  • packages/app/src/context/command-keybind.test.ts
  • packages/app/src/context/command.tsx
  • packages/app/src/context/file.tsx
  • packages/app/src/context/global-sdk.tsx
  • packages/app/src/context/global-sync.test.ts
  • packages/app/src/context/global-sync.tsx
  • packages/app/src/context/global-sync/bootstrap.ts
  • packages/app/src/context/global-sync/child-store.test.ts
  • packages/app/src/context/global-sync/child-store.ts
  • packages/app/src/context/global-sync/event-reducer.test.ts
  • packages/app/src/context/global-sync/event-reducer.ts
  • packages/app/src/context/global-sync/session-cache.test.ts
  • packages/app/src/context/global-sync/session-cache.ts
  • packages/app/src/context/global-sync/session-prefetch.test.ts
  • packages/app/src/context/global-sync/session-prefetch.ts
  • packages/app/src/context/global-sync/types.ts
  • packages/app/src/context/global-sync/utils.test.ts
  • packages/app/src/context/global-sync/utils.ts
  • packages/app/src/context/highlights.tsx
  • packages/app/src/context/language.tsx
  • packages/app/src/context/layout.tsx
  • packages/app/src/context/local.tsx
  • packages/app/src/context/model-variant.test.ts
  • packages/app/src/context/model-variant.ts
  • packages/app/src/context/notification-index.ts
  • packages/app/src/context/notification.test.ts
  • packages/app/src/context/notification.tsx
  • packages/app/src/context/permission-auto-respond.test.ts
  • packages/app/src/context/permission-auto-respond.ts
  • packages/app/src/context/permission.tsx
  • packages/app/src/context/platform.tsx
  • packages/app/src/context/prompt.tsx
  • packages/app/src/context/server.tsx
  • packages/app/src/context/settings.tsx
  • packages/app/src/context/sync-optimistic.test.ts
  • packages/app/src/context/sync.tsx
  • packages/app/src/context/terminal-title.ts
  • packages/app/src/context/terminal.test.ts
  • packages/app/src/context/terminal.tsx
  • packages/app/src/entry.tsx
  • packages/app/src/hooks/use-providers.ts
  • packages/app/src/i18n/ar.ts
  • packages/app/src/i18n/br.ts
  • packages/app/src/i18n/bs.ts
  • packages/app/src/i18n/da.ts
  • packages/app/src/i18n/de.ts
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/es.ts
  • packages/app/src/i18n/fr.ts
  • packages/app/src/i18n/ja.ts
  • packages/app/src/i18n/ko.ts
  • packages/app/src/i18n/no.ts
  • packages/app/src/i18n/parity.test.ts
  • packages/app/src/i18n/pl.ts
  • packages/app/src/i18n/ru.ts
  • packages/app/src/i18n/th.ts
  • packages/app/src/i18n/tr.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/i18n/zht.ts
  • packages/app/src/index.css
  • packages/app/src/index.ts
  • packages/app/src/pages/directory-layout.tsx
  • packages/app/src/pages/error.tsx
  • packages/app/src/pages/home.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/layout/deep-links.ts
  • packages/app/src/pages/layout/helpers.test.ts
  • packages/app/src/pages/layout/helpers.ts
  • packages/app/src/pages/layout/sidebar-items.tsx
  • packages/app/src/pages/layout/sidebar-project-helpers.test.ts
  • packages/app/src/pages/layout/sidebar-project-helpers.ts
  • packages/app/src/pages/layout/sidebar-project.tsx
  • packages/app/src/pages/layout/sidebar-shell-helpers.ts
  • packages/app/src/pages/layout/sidebar-shell.test.ts
  • packages/app/src/pages/layout/sidebar-shell.tsx
  • packages/app/src/pages/layout/sidebar-workspace-helpers.ts
  • packages/app/src/pages/layout/sidebar-workspace.test.ts
  • packages/app/src/pages/layout/sidebar-workspace.tsx
  • packages/app/src/pages/session.tsx
  • packages/app/src/pages/session/composer/index.ts
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/app/src/pages/session/composer/session-composer-state.test.ts
  • packages/app/src/pages/session/composer/session-composer-state.ts
  • packages/app/src/pages/session/composer/session-followup-dock.tsx
  • packages/app/src/pages/session/composer/session-question-dock.tsx
  • packages/app/src/pages/session/composer/session-request-tree.ts
  • packages/app/src/pages/session/composer/session-revert-dock.tsx
  • packages/app/src/pages/session/composer/session-todo-dock.tsx
  • packages/app/src/pages/session/file-tabs.tsx
  • packages/app/src/pages/session/helpers.test.ts
  • packages/app/src/pages/session/helpers.ts
  • packages/app/src/pages/session/message-id-from-hash.ts
  • packages/app/src/pages/session/message-timeline.tsx
  • packages/app/src/pages/session/review-tab.tsx
  • packages/app/src/pages/session/scroll-spy.test.ts
  • packages/app/src/pages/session/scroll-spy.ts
  • packages/app/src/pages/session/session-command-helpers.ts
  • packages/app/src/pages/session/session-layout.ts
  • packages/app/src/pages/session/session-mobile-tabs.tsx
  • packages/app/src/pages/session/session-model-helpers.test.ts
  • packages/app/src/pages/session/session-model-helpers.ts
  • packages/app/src/pages/session/session-prompt-dock.test.ts
  • packages/app/src/pages/session/session-prompt-helpers.ts
  • packages/app/src/pages/session/session-side-panel.tsx
  • packages/app/src/pages/session/terminal-label.ts
  • packages/app/src/pages/session/terminal-panel.tsx
  • packages/app/src/pages/session/use-session-commands.test.ts
  • packages/app/src/pages/session/use-session-commands.tsx
  • packages/app/src/pages/session/use-session-hash-scroll.test.ts
  • packages/app/src/pages/session/use-session-hash-scroll.ts
  • packages/app/src/testing/model-selection.ts
  • packages/app/src/testing/prompt.ts
  • packages/app/src/testing/session-composer.ts
  • packages/app/src/testing/terminal.ts
  • packages/app/src/theme-preload.test.ts
  • packages/app/src/utils/agent.ts
  • packages/app/src/utils/dom.ts
  • packages/app/src/utils/index.ts
  • packages/app/src/utils/notification-click.test.ts
  • packages/app/src/utils/notification-click.ts
  • packages/app/src/utils/persist.test.ts
  • packages/app/src/utils/persist.ts
  • packages/app/src/utils/prompt.test.ts
  • packages/app/src/utils/server-errors.test.ts
  • packages/app/src/utils/server-errors.ts
  • packages/app/src/utils/server-health.ts
  • packages/app/src/utils/sound.ts
  • packages/app/src/utils/speech.ts
  • packages/app/src/utils/time.ts
  • packages/app/tsconfig.json
  • packages/app/vite.js
  • packages/console/app/package.json
  • packages/console/app/script/generate-sitemap.ts
  • packages/console/app/src/app.tsx
  • packages/console/app/src/component/footer.tsx
  • packages/console/app/src/component/header.tsx
  • packages/console/app/src/component/icon.tsx
  • packages/console/app/src/component/modal.css
  • packages/console/app/src/config.ts
  • packages/console/app/src/i18n/ar.ts
  • packages/console/app/src/i18n/br.ts
  • packages/console/app/src/i18n/da.ts
  • packages/console/app/src/i18n/de.ts
  • packages/console/app/src/i18n/en.ts
  • packages/console/app/src/i18n/es.ts
  • packages/console/app/src/i18n/fr.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/memory-20695

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

Hey! Your PR title fix/memory 20695 doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, opencode).

See CONTRIBUTING.md for details.

@github-actions
Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces extensive updates across the repository, including a major refactor of the application's startup and synchronization logic, the addition of a performance debug bar, and improved terminal reconnection handling. It also expands internationalization support with new translations and glossaries, updates core dependencies like the AI SDK and Drizzle, and establishes a specification for simplifying reactive effects. Feedback was provided regarding a syntax error in a Tailwind CSS selector within the server selection dialog.

}
: undefined
}
class="px-5 [&_[data-slot=list-search-wrapper]]:w-full [&_[data-slot=list-scroll]]h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:min-h-14 [&_[data-slot=list-item]]:p-3 [&_[data-slot=list-item]]:!bg-transparent"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a syntax error in the Tailwind arbitrary value selector. A colon is missing before h-[300px]. It should be [&_[data-slot=list-scroll]]:h-[300px] (or max-h if the previous behavior was intended) to correctly target the nested element.

Suggested change
class="px-5 [&_[data-slot=list-search-wrapper]]:w-full [&_[data-slot=list-scroll]]h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:min-h-14 [&_[data-slot=list-item]]:p-3 [&_[data-slot=list-item]]:!bg-transparent"
class="px-5 [&_[data-slot=list-search-wrapper]]:w-full [&_[data-slot=list-scroll]]:h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:min-h-14 [&_[data-slot=list-item]]:p-3 [&_[data-slot=list-item]]:!bg-transparent"

…nomalyco#20695)

Live-caught crash @ 2026-04-21T00:17:07Z: RSS=4.03 GB after 60 s of runtime
on session resume. Our auto-heap-snapshot fired and produced a 646 MB + 413 MB
pair before a bun int3 assertion killed the process 8 min later.

Root cause: /session/<id>/diff is fetched 45 times at TUI startup (once per
session in sidebar) and each fetch loads a Snapshot.FileDiff[] with full
before/after file contents. Disk measured 6.5 GB across 7438 session_diff
files, largest 2.1 GB with 780 before/after string pairs.

Per Oracle recommendation:

1. sync.tsx: drop session.diff() from the per-session eager Promise.all;
   bus events still populate the store reactively when diffs actually change.
   The TUI itself never renders diffs (only the plugin API reads them), so
   there's no user-visible regression.

2. Snapshot.capFileDiffs(): replace before/after with empty strings when
   either field exceeds 256 KB. UI (file.tsx large() threshold @ 500 K chars)
   already disables line-level diffing past that anyway; empty strings
   integrate cleanly with session-review.tsx's beforeText().length === 0
   added/deleted check.

3. Applied at write time in summary.ts:122 + revert.ts:78 BEFORE both
   storage.write() and bus.publish() so cached UI state never sees the
   uncapped form.

4. Applied at read time in Session.diff() + SessionSummary.diff() so the
   existing 6.5 GB of legacy data on disk is capped progressively on first
   access without needing a migration script.

Skipped PR anomalyco#21244 (unified-patch storage): the @pierre/diffs FileDiff
component requires full before/after strings, so reconstructing at render
time would restore the memory spike. Capping at the source is simpler,
doesn't diverge further from upstream, and remains compatible with a future
anomalyco#21244 port.

Ref: anomalyco#20695
… diff pipeline

FileDiff schema gains patch?: string. computeDiff now generates a unified
diff string via createTwoFilesPatch for each non-binary file diff. Binary
files skip patch generation (patch: undefined).

The patch field enables @pierre/diffs patch-mode rendering (isPartial: true)
which uses ~5-10% of the memory vs full before/after strings. FIELD_CAP
comment updated to document its defense-in-depth role.

Ref: anomalyco#20695
DiffViewer and DiffSSRViewer now check for a patch prop. When present,
getSingularPatch() parses it into a FileDiffMetadata (isPartial: true)
and passes it directly to FileDiff.render()/hydrate() — no full file
contents needed. Falls back to oldFile/newFile on parse error or when
patch is absent (legacy data).

session-review.tsx passes patch={item().patch} through to File component.
SDK types regenerated to include patch field.

Ref: anomalyco#20695
9 unit tests (capFileDiffs with/without patch, boundary, mixed arrays)
4 integration tests (large file pipeline, patch size savings, mixed array,
JSON roundtrip). All verify getSingularPatch produces valid isPartial
FileDiffMetadata from the generated patches.

Ref: anomalyco#20695
@github-actions
Copy link
Copy Markdown

This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window.

Feel free to open a new pull request that follows our guidelines.

@devactivity-app
Copy link
Copy Markdown

Pull Request Summary by devActivity

Metrics

Cycle Time: 4h 17m Coding Time: 1m Review Time: 4h 13m Comments: 4

Achievements

…o#20695)

Bun 1.3.13-canary's default SIGINT/SIGTERM handler crashes with
SIGSEGV at 0xF038EC when opentui's FFI JSCallback objects are torn
down during process exit. Classic N-API-style UAF — the Zig side
holds raw function pointers into JSCallback thunks that get freed
during bun's exit cleanup, triggering a jump into non-executable
.eh_frame memory.

Install explicit SIGINT/SIGTERM/SIGHUP handlers that:
  1. Restore terminal state (raw-mode off, alt-buffer exit, show cursor,
     disable mouse tracking modes)
  2. Call process.exit() immediately with the correct signal exit code

This bypasses bun's default exit path before the FFI teardown can
crash. Verified via reliable reproducer (session ses_253dacbc... +
SIGINT after 15s): pre-patch crashed deterministically at 0xF038EC;
post-patch exits cleanly with code 130.

Related bun issues:
  - oven-sh/bun#27471 (N-API vtable corruption)
  - oven-sh/bun#27003 (N-API cleanup race)
  - oven-sh/bun#29412 (DFG JIT crash)

Ref: anomalyco#20695
@coleleavitt coleleavitt reopened this Apr 21, 2026
@coleleavitt coleleavitt marked this pull request as ready for review April 21, 2026 20:37
@coleleavitt coleleavitt merged commit 3bc71f9 into dev Apr 21, 2026
4 of 11 checks passed
@devactivity-app
Copy link
Copy Markdown

Pull Request Summary by devActivity

Metrics

Cycle Time: 20h 31m Coding Time: 1m Review Time: 20h 27m Comments: 5

Achievements

Copy link
Copy Markdown

@llamapreview llamapreview Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Code Review by LlamaPReview

🎯 TL;DR & Recommendation

Recommendation: Request Changes

This PR introduces a critical CI-breaking syntax error in the test workflow configuration, which will cause all subsequent runs to fail immediately. Additionally, it expands signing and Electron build pipelines with increased complexity that risks configuration errors.

🌟 Strengths

  • Proactively denies agent edits to migration files, enhancing security.
  • Updates team assignments and tool logic to reflect current availability.
Priority File Category Impact Summary (≤12 words) Anchors
P0 .github/workflows/test.yml Bug Broken CI due to invalid GitHub Actions syntax causing workflow failures.
P2 .github/.../docs-locale-sync.yml Maintainability Overly permissive agent permissions could allow unintended file edits.
P2 .opencode/tool/github-triage.ts Maintainability Default assignee and override logic may confuse users for web issues.
P2 .opencode/opencode.jsonc Security Denies agent edits to migration files, preventing data corruption.
P2 .github/workflows/publish.yml Architecture Complex signing and Electron steps increase risk of configuration errors. path:setup-bun/action.yml

🔍 Notable Themes

  • CI/CD Configuration Stability: The P0 syntax error and the expanded signing workflow introduce immediate and latent risks to build reliability.
  • Agent Security and Clarity: Permission changes and tool updates improve security but require validation to avoid unintended access or user confusion.

📈 Risk Diagram

This diagram illustrates the immediate workflow failure due to the invalid concurrency group syntax.

sequenceDiagram
    participant GA as GitHub Actions
    participant WF as Workflow Runner
    GA->>WF: Trigger test workflow
    WF->>WF: Parse concurrency group expression
    note over WF: R1(P0): Invalid 'case' function causes syntax error
    WF-->>GA: Workflow fails immediately
Loading
⚠️ **Unanchored Suggestions (Manual Review Recommended)**

The following suggestions could not be precisely anchored to a specific line in the diff. This can happen if the code is outside the changed lines, has been significantly refactored, or if the suggestion is a general observation. Please review them carefully in the context of the full file.


📁 File: .github/workflows/docs-locale-sync.yml

Speculative: The workflow now directly invokes the opencode CLI via a shell command, replacing the previous dedicated GitHub Action (sst/opencode/github). This increases flexibility but also introduces new dependencies: the opencode CLI must be installed (via a prior curl command) and the OPENCODE_API_KEY must be valid. The permission configuration has been drastically simplified from a granular, deny-by-default policy to an allow-all policy ("read": "allow", "edit": "allow", ...). This could inadvertently grant the agent broader access than intended during translation tasks, potentially allowing modifications outside the target locale directories. While no immediate breakage is certain, the change represents a significant relaxation of security controls and should be reviewed to ensure it aligns with the principle of least privilege.

Related Code:

- name: Sync locale docs with OpenCode
  if: steps.changes.outputs.has_changes == 'true'
  env:
    OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
    OPENCODE_CONFIG_CONTENT: |
      {
        "permission": {
          "*": "deny",
          "read": "allow",
          "edit": "allow",
          "glob": "allow",
          "task": "allow"
        }
      }
  run: |
    opencode run --agent docs --model opencode/gpt-5.3-codex <<'EOF'
      Update localized docs to match the latest English docs changes.
      ...
    EOF

📁 File: .opencode/tool/github-triage.ts

The github-triage tool's team list has been updated, notably adding rekram1-node back to the tui and core teams and setting them as the new default assignee. This change aligns with the PR description's note "rekram1-node is no longer on vacation." However, the tool's logic for assigning web issues (line 68) has also been adjusted: const assignee = nix ? "rekram1-node" : web ? pick(TEAM.desktop) : args.assignee. This means for a web issue (non-nix), the assignee will be randomly picked from the desktop team, overriding the user-provided or default args.assignee. This behavior may be intentional but is non-obvious; a user specifying an assignee for a web issue will be ignored. The interaction between the default assignee and the web/nix override logic should be documented or made explicit in the tool's description.

Related Code:

const TEAM = {
  desktop: ["adamdotdevin", "iamdavidhill", "Brendonovich", "nexxeln"],
  zen: ["fwang", "MrMushrooooom"],
  tui: ["thdxr", "kommander", "rekram1-node"],
  core: ["thdxr", "rekram1-node", "jlongster"],
  docs: ["R44VC0RP"],
  windows: ["Hona"],
} as const
// ...
args: {
  assignee: tool.schema
    .enum(ASSIGNEES as [string, ...string[]])
    .describe("The username of the assignee")
    .default("rekram1-node"),


💡 Have feedback? We'd love to hear it in our GitHub Discussions.
✨ This review was generated by LlamaPReview Advanced, which is free for all open-source projects. Learn more.

Comment on lines +10 to +14
concurrency:
# Keep every run on dev so cancelled checks do not pollute the default branch
# commit history. PRs and other branches still share a group and cancel stale runs.
group: ${{ case(github.ref == 'refs/heads/dev', format('{0}-{1}', github.workflow, github.run_id), format('{0}-{1}', github.workflow, github.event.pull_request.number || github.ref)) }}
cancel-in-progress: true
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 | Confidence: High

The concurrency.group expression uses a non-existent case function in GitHub Actions. The GitHub Actions expression language does not support a case function; valid functions include format, contains, startsWith, endsWith, fromJSON, toJSON, etc. This syntax error will cause the workflow to fail immediately on any trigger, breaking all CI runs for the test workflow. The intended logic is to create a unique group for each run on the dev branch (to avoid cancellation) and a shared group for PRs/other branches. The expression must be rewritten using conditional logic (e.g., using the ternary operator ? : or separate if conditions).

Suggested change
concurrency:
# Keep every run on dev so cancelled checks do not pollute the default branch
# commit history. PRs and other branches still share a group and cancel stale runs.
group: ${{ case(github.ref == 'refs/heads/dev', format('{0}-{1}', github.workflow, github.run_id), format('{0}-{1}', github.workflow, github.event.pull_request.number || github.ref)) }}
cancel-in-progress: true
concurrency:
group: ${{ github.ref == 'refs/heads/dev' && format('{0}-{1}', github.workflow, github.run_id) || format('{0}-{1}', github.workflow, github.event.pull_request.number || github.ref) }}
cancel-in-progress: true

Comment on lines +269 to +275
- name: Azure login
if: runner.os == 'Windows'
uses: azure/login@v2
with:
client-id: ${{ env.AZURE_CLIENT_ID }}
tenant-id: ${{ env.AZURE_TENANT_ID }}
subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 | Confidence: Medium

Speculative: The publish workflow has undergone a substantial expansion to integrate Azure artifact signing for Windows CLI and Electron builds, and to add Electron as a new desktop target. While the changes appear comprehensive, the increased complexity (over 300 lines added) and introduction of new external dependencies (Azure services, electron-builder) heighten the risk of configuration errors, especially in conditional steps across multiple OS targets. For example, the OPENCODE_CLI_ARTIFACT environment variable logic (${{ (runner.os == 'Windows' && 'opencode-cli-windows') || 'opencode-cli' }}) must correctly correspond to the uploaded artifact names. A mismatch could cause builds to fail or use unsigned binaries. Furthermore, the workflow now runs on multiple runner types (including windows-2025 for ARM64), which may have different toolchains or environment defaults. These changes should be validated with a dry-run on a non-production branch to ensure all signing and packaging steps execute correctly across all matrix combinations.

Comment thread .opencode/opencode.jsonc
@@ -5,6 +5,11 @@
"options": {},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Contextual Comment]
This comment refers to code near real line 1. Anchored to nearest_changed(5) line 5.


P2 | Confidence: High

A new deny rule has been added to prevent edits to migration files (packages/opencode/migration/*). This is a proactive security and stability measure to prevent agents from accidentally modifying database migration scripts, which could lead to data corruption or broken deployments. The change is well-scoped and follows the principle of least privilege. No negative impact is expected, as migrations are typically auto-generated and should not be manually edited by agents.

coleleavitt added a commit that referenced this pull request Apr 21, 2026
…mory telemetry (anomalyco#20695)

Follow-up to PR #5. Heap dump analysis (PID 723876, 10 pre/post-GC pairs)
showed closure allocations from registry.tools() consuming memory faster
than GC could reclaim under rapid LLM messaging.

Changes:
- tool/registry.ts: cache tool.init({agent}) result per (toolID, agent.name)
  in InstanceState. Before: every ToolRegistry.tools() call allocated 17+
  fresh wrapper closures via tool.init. After: init runs once per
  (tool, agent), closures reused across messages.
- tool/registry.ts: register() now invalidates init cache for the updated
  tool id so plugin reloads propagate.
- snapshot/index.ts: capFileDiffs now logs when it fires (capped count,
  saved bytes, retained bytes) so oversized sessions are visible in
  dev.log. Threshold: any capping event OR >500 diffs OR >64 MB retained.
- app session-cache.ts: new sessionDiffStats() + warnOversizedSessionDiff()
  helpers. Logs sessions exceeding 500 diffs or 64 MB. dropSessionCaches()
  logs total MB freed when eviction reclaims >16 MB.
- script/heap-analyze.ts: new CLI for retention analysis on existing
  .heapsnapshot files. Computes node-type histogram, top-N largest
  retained objects, closure name distribution, top-N most-referenced
  nodes, top-N string frequencies. Run: bun run script/heap-analyze.ts
  <path> [topN=20].

Verified: bun typecheck clean on opencode+app. 49 tests pass (snapshot
patch-mode + pipeline, tool-define, registry, global-sync).

Note: LEAK A (session_diff cache) was verified already-mitigated by PR #5's
capFileDiffs + patch-mode (5-10% of raw). Telemetry added defensively.
LEAK B (closure allocation) is addressed by the init cache here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.