Skip to content

fix(sandbox): make file-diff failures observable and correct (#914)#917

Merged
AlemTuzlak merged 4 commits into
mainfrom
fix/sandbox-file-diff-observability
Jul 10, 2026
Merged

fix(sandbox): make file-diff failures observable and correct (#914)#917
AlemTuzlak merged 4 commits into
mainfrom
fix/sandbox-file-diff-observability

Conversation

@AlemTuzlak

@AlemTuzlak AlemTuzlak commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #892, closing #914. A post-merge review of the sandbox file-diff subsystem found a cluster of correctness + observability gaps sharing one root cause: buildFileHookEvent and the watcher were built on a "never throws, fall back to ''/empty, don't log" contract, so a git/exec/fs failure silently became empty (or wrong) data — the worst failure mode for an observability feature.

Severity: medium — the feature is new and opt-in (no prod consumers yet), no data-loss/security angle beyond the git-ignored-content fix below.

Correctness

  • Untracked files no longer stream empty diffs (sandbox file-diff: make failures observable + correct (follow-up to #892) #914.1). git diff <sha> -- <path> shows nothing for a file git isn't tracking, so a file the agent creates — and every later edit to it — used to emit an empty diff. diff() now synthesizes an add-patch keyed on tracked-ness at the baseline (probed via the git show exit code), not on event.type. A tracked file identical to the baseline still diffs empty, and a transient probe failure no longer fabricates a bogus add-patch. Synthesized patches follow git's add-file shape (diff --git header, new file mode, \ No newline at end of file marker, header-only for empty files).
  • No more phantom create/delete storms (sandbox file-diff: make failures observable + correct (follow-up to #892) #914.2). A failed find poll now preserves the previous snapshot instead of collapsing to empty; a failed/partial initial poll seeds without diffing (re-baselining on the first complete poll); a partial (permission-denied) poll is merged, not diffed, so a transiently-unreadable subtree isn't reported as deletions.
  • Watcher/enrichment root alignment. The watcher now watches definition.workspace.root (it was defaulting to /workspace while enrichment relativized against workspace.root).
  • Teardown always destroys. A rejecting watcher.stop() can no longer skip definition.destroy() on the abort path; the native watcher re-seeds if its initial listing fails.

Observability (#914.3)

Every swallowed git/exec/fs failure — across the diff accessors, both watcher paths (exec-poll + native fs.watch), the git-baseline capture, and per-hook dispatch — now logs before falling back: real anomalies under errors/warn (on by default), expected-empty conditions under the sandbox debug category. This also aligns the middleware SKILL's hook-error claim with actual behavior (it logs under errors, matching the engine's run-scoped path).

Security-adjacent

  • git-ignored files are withheld from the diff feed. An agent-created .env/credentials file still fires its file event (you're notified it changed) but diff() returns '', so its contents are never surfaced in the stream.

Tests (#914.4)

  • The previously-untested pendingDiffs teardown drain now has deferred-promise tests for onFinish/onAbort/onError (verified to fail if the drain is removed).
  • New coverage for: untracked create+edit synthesis, tracked-identical no-op, git-ignored withholding, no-trailing-newline/multi-line/empty synthesized patches, failed/partial/initial-throw poll handling, native re-seed, workspace.root wiring, destroy-on-rejecting-stop, and every new log path.

Review

Ran through a 3-round automated review-fix loop (correctness / silent-failure / test-coverage / comment / type-design lenses) to convergence — every finding fixed and re-confirmed, all fixes covered by tests verified to fail on revert.

Verification

test:types, test:eslint (src), test:build, and test:docs clean; 189/190 ai-sandbox unit tests pass (the 1 failure is a pre-existing Windows path-separator issue in harness-cwd.test.ts, unrelated to this change). Changeset included (@tanstack/ai-sandbox patch).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • File diffs are now more reliable and easier to inspect, including clearer patch output for newly created content.
    • Sandbox file reading/diffing and watcher behavior are more observable via structured warnings/debug logs.
  • Bug Fixes

    • Fixed empty or incorrect diffs for newly created, edited, ignored, and untracked files (including non-git workspaces and “git-ignored” cases).
    • Improved file watching to reduce misclassifying edits as creates/deletes and to stabilize polling/recovery.
  • Documentation

    • Updated sandbox observability docs and SKILL guidance to clarify edge-case behavior.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 889ba55a-edd1-4bba-8996-79ff76b4dd93

📥 Commits

Reviewing files that changed from the base of the PR and between ea598c7 and a45094c.

📒 Files selected for processing (2)
  • packages/ai-sandbox/src/watch.ts
  • packages/ai-sandbox/tests/watch.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ai-sandbox/tests/watch.test.ts

📝 Walkthrough

Walkthrough

This PR improves sandbox diff correctness by synthesizing git-style add-patches for untracked files, withholding diffs for git-ignored files, and making watcher and teardown paths log failures instead of silently dropping them.

Changes

Sandbox observability

Layer / File(s) Summary
Diff synthesis and logging
packages/ai-sandbox/src/file-diff.ts, packages/ai-sandbox/tests/fakes.ts, packages/ai-sandbox/tests/file-diff.test.ts
file-diff.ts now builds git-style add-patches for untracked content, returns empty diffs for tracked or ignored cases, and logs accessor/probe failures; tests add logger capture and cover the new diff paths.
Watcher reseeding and polling
packages/ai-sandbox/src/watch.ts, packages/ai-sandbox/tests/watch.test.ts
Native and exec-poll watchers now reseed or preserve snapshots after failed or partial listings, avoiding phantom create/delete events; tests cover recovery and logging behavior.
Middleware logging and teardown
packages/ai-sandbox/src/middleware.ts, packages/ai-sandbox/tests/with-sandbox-hooks.test.ts
Middleware threads a logger through baseline capture, hook dispatch, and watcher setup, and drains pending diffs during finish/abort/error; tests cover deferred diffs, hook logging, and abort handling.
Documentation and changeset updates
.changeset/sandbox-file-diff-observability.md, docs/config.json, docs/sandbox/observability.md, packages/ai/skills/ai-core/middleware/SKILL.md
Docs and metadata are updated to describe the new diff behavior and logging categories.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • TanStack/ai#892: Modifies the same sandbox file-diff and middleware flow that this PR further extends with diff synthesis and logging.

Suggested reviewers: tombeckenham

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it does not follow the required template headings or checklist/release-impact checkbox format. Reformat the PR body to use the required 🎯 Changes, ✅ Checklist, and 🚀 Release Impact sections, and fill in the checklist items.
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: sandbox file-diff correctness and observability fixes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sandbox-file-diff-observability

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.

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

6 package(s) bumped directly, 39 bumped as dependents.

🟥 Major bumps

Package Version Reason
@tanstack/ai-gemini 0.19.1 → 1.0.0 Changeset
@tanstack/ai-openai 0.16.0 → 1.0.0 Changeset
@tanstack/ai-react 0.16.4 → 1.0.0 Changeset
@tanstack/ai-sandbox 0.2.2 → 1.0.0 Changeset
@tanstack/ai-acp 0.2.1 → 1.0.0 Dependent
@tanstack/ai-angular 0.2.3 → 1.0.0 Dependent
@tanstack/ai-anthropic 0.16.1 → 1.0.0 Dependent
@tanstack/ai-bedrock 0.1.2 → 1.0.0 Dependent
@tanstack/ai-claude-code 0.2.1 → 1.0.0 Dependent
@tanstack/ai-code-mode 0.3.6 → 1.0.0 Dependent
@tanstack/ai-code-mode-skills 0.3.9 → 1.0.0 Dependent
@tanstack/ai-codex 0.2.1 → 1.0.0 Dependent
@tanstack/ai-elevenlabs 0.2.32 → 1.0.0 Dependent
@tanstack/ai-fal 0.9.10 → 1.0.0 Dependent
@tanstack/ai-grok 0.14.7 → 1.0.0 Dependent
@tanstack/ai-grok-build 0.2.1 → 1.0.0 Dependent
@tanstack/ai-groq 0.5.1 → 1.0.0 Dependent
@tanstack/ai-isolate-node 0.1.45 → 1.0.0 Dependent
@tanstack/ai-isolate-quickjs 0.1.45 → 1.0.0 Dependent
@tanstack/ai-mistral 0.2.1 → 1.0.0 Dependent
@tanstack/ai-ollama 0.8.14 → 1.0.0 Dependent
@tanstack/ai-opencode 0.2.1 → 1.0.0 Dependent
@tanstack/ai-openrouter 0.15.8 → 1.0.0 Dependent
@tanstack/ai-preact 0.10.3 → 1.0.0 Dependent
@tanstack/ai-react-ui 0.8.13 → 1.0.0 Dependent
@tanstack/ai-sandbox-cloudflare 0.2.2 → 1.0.0 Dependent
@tanstack/ai-sandbox-daytona 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-docker 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-local-process 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-sprites 0.2.1 → 1.0.0 Dependent
@tanstack/ai-sandbox-vercel 0.2.0 → 1.0.0 Dependent
@tanstack/ai-solid 0.14.3 → 1.0.0 Dependent
@tanstack/ai-solid-ui 0.7.12 → 1.0.0 Dependent
@tanstack/ai-svelte 0.14.3 → 1.0.0 Dependent
@tanstack/ai-vue 0.14.3 → 1.0.0 Dependent
@tanstack/openai-base 0.9.7 → 1.0.0 Dependent

🟨 Minor bumps

Package Version Reason
@tanstack/ai 0.40.0 → 0.41.0 Changeset
@tanstack/ai-client 0.20.0 → 0.21.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/ai-devtools-core 0.4.22 → 0.4.23 Dependent
@tanstack/ai-isolate-cloudflare 0.2.36 → 0.2.37 Dependent
@tanstack/ai-mcp 0.2.3 → 0.2.4 Dependent
@tanstack/ai-vue-ui 0.2.31 → 0.2.32 Dependent
@tanstack/preact-ai-devtools 0.1.65 → 0.1.66 Dependent
@tanstack/react-ai-devtools 0.2.65 → 0.2.66 Dependent
@tanstack/solid-ai-devtools 0.2.65 → 0.2.66 Dependent

@nx-cloud

nx-cloud Bot commented Jul 8, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit a45094c

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 25s View ↗

☁️ Nx Cloud last updated this comment at 2026-07-09 11:13:12 UTC

@pkg-pr-new

pkg-pr-new Bot commented Jul 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@917

@tanstack/ai-acp

npm i https://pkg.pr.new/@tanstack/ai-acp@917

@tanstack/ai-angular

npm i https://pkg.pr.new/@tanstack/ai-angular@917

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@917

@tanstack/ai-bedrock

npm i https://pkg.pr.new/@tanstack/ai-bedrock@917

@tanstack/ai-claude-code

npm i https://pkg.pr.new/@tanstack/ai-claude-code@917

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@917

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@917

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/@tanstack/ai-code-mode-skills@917

@tanstack/ai-codex

npm i https://pkg.pr.new/@tanstack/ai-codex@917

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@917

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@917

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@917

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@917

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@917

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@917

@tanstack/ai-grok-build

npm i https://pkg.pr.new/@tanstack/ai-grok-build@917

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@917

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@917

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@917

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@917

@tanstack/ai-mcp

npm i https://pkg.pr.new/@tanstack/ai-mcp@917

@tanstack/ai-mistral

npm i https://pkg.pr.new/@tanstack/ai-mistral@917

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@917

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@917

@tanstack/ai-opencode

npm i https://pkg.pr.new/@tanstack/ai-opencode@917

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@917

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@917

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@917

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@917

@tanstack/ai-sandbox

npm i https://pkg.pr.new/@tanstack/ai-sandbox@917

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-sandbox-cloudflare@917

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/@tanstack/ai-sandbox-daytona@917

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/@tanstack/ai-sandbox-docker@917

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/@tanstack/ai-sandbox-local-process@917

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/@tanstack/ai-sandbox-sprites@917

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/@tanstack/ai-sandbox-vercel@917

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@917

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@917

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@917

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@917

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@917

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@917

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@917

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@917

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@917

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@917

commit: a45094c

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ai-sandbox/src/file-diff.ts`:
- Around line 189-192: The baseSha === '' branch in file-diff.ts bypasses the
ignore filtering used elsewhere, so create/change events can leak git-ignored
contents through the diff stream. Update the diff() flow to reuse the same git
check-ignore logic here, or delegate this path through
synthesizeIfUntracked(rel) before calling synthesizeAddPatch, and keep the
handling aligned with the existing baseSha !== '' branch.

In `@packages/ai-sandbox/tests/with-sandbox-hooks.test.ts`:
- Around line 7-8: The test file has duplicate type-only imports from the same
`@tanstack/ai` module, which triggers `import/no-duplicates`. Consolidate the
`SandboxFileEvent` and `ChatMiddlewareContext` imports into a single type import
at the top of the file, keeping the existing identifiers unchanged so the rest
of `with-sandbox-hooks.test.ts` continues to compile.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9dcb09fe-1f2d-40de-8c32-6457f7701312

📥 Commits

Reviewing files that changed from the base of the PR and between f2ded50 and c01c1d4.

📒 Files selected for processing (11)
  • .changeset/sandbox-file-diff-observability.md
  • docs/config.json
  • docs/sandbox/observability.md
  • packages/ai-sandbox/src/file-diff.ts
  • packages/ai-sandbox/src/middleware.ts
  • packages/ai-sandbox/src/watch.ts
  • packages/ai-sandbox/tests/fakes.ts
  • packages/ai-sandbox/tests/file-diff.test.ts
  • packages/ai-sandbox/tests/watch.test.ts
  • packages/ai-sandbox/tests/with-sandbox-hooks.test.ts
  • packages/ai/skills/ai-core/middleware/SKILL.md

Comment on lines 189 to +192
const diff = async (): Promise<string> => {
if (baseSha === '') {
if (event.type === 'delete') return ''
return synthesizeAddPatch(event.path, await after())
return synthesizeAddPatch(relTo(root, event.path), await after())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Git-ignored file contents can leak when baseSha === '' in a fresh git init repo with no HEAD. This branch emits full add-patches for create/change events without the git check-ignore gate used in the baseSha !== '' path, so an ignored .env could be streamed into the diff feed. Reuse that ignore check here, or route this case through synthesizeIfUntracked(rel).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-sandbox/src/file-diff.ts` around lines 189 - 192, The baseSha ===
'' branch in file-diff.ts bypasses the ignore filtering used elsewhere, so
create/change events can leak git-ignored contents through the diff stream.
Update the diff() flow to reuse the same git check-ignore logic here, or
delegate this path through synthesizeIfUntracked(rel) before calling
synthesizeAddPatch, and keep the handling aligned with the existing baseSha !==
'' branch.

Comment on lines 7 to 8
import type { SandboxFileEvent } from '@tanstack/ai'
import type { ChatMiddlewareContext } from '@tanstack/ai'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Merge the duplicate @tanstack/ai type imports — ESLint fails the build.

import/no-duplicates reports both lines as errors. Combine them into a single type import.

♻️ Proposed fix
-import type { SandboxFileEvent } from '`@tanstack/ai`'
-import type { ChatMiddlewareContext } from '`@tanstack/ai`'
+import type { SandboxFileEvent, ChatMiddlewareContext } from '`@tanstack/ai`'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { SandboxFileEvent } from '@tanstack/ai'
import type { ChatMiddlewareContext } from '@tanstack/ai'
import type { SandboxFileEvent, ChatMiddlewareContext } from '`@tanstack/ai`'
🧰 Tools
🪛 ESLint

[error] 7-7: '@tanstack/ai' imported multiple times.

(import/no-duplicates)


[error] 8-8: '@tanstack/ai' imported multiple times.

(import/no-duplicates)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-sandbox/tests/with-sandbox-hooks.test.ts` around lines 7 - 8, The
test file has duplicate type-only imports from the same `@tanstack/ai` module,
which triggers `import/no-duplicates`. Consolidate the `SandboxFileEvent` and
`ChatMiddlewareContext` imports into a single type import at the top of the
file, keeping the existing identifiers unchanged so the rest of
`with-sandbox-hooks.test.ts` continues to compile.

Source: Linters/SAST tools

AlemTuzlak and others added 3 commits July 9, 2026 20:34
Follow-up to #892. The sandbox file-diff subsystem was built on a
"never throws, fall back to ''/empty, don't log" contract, so a
git/exec/fs failure silently became empty/wrong data.

- diff(): synthesize an add-patch for any file git isn't tracking
  (a created file AND every later edit), keyed on tracked-ness at the
  baseline rather than event.type; a tracked file identical to the
  baseline still diffs empty, and a transient git-show probe failure no
  longer fabricates a bogus add-patch. Synthesized patches follow git's
  add-file shape (diff --git header, new file mode, no-newline marker).
- git-ignored files are withheld from the diff feed (the event still
  fires; diff() returns '') so a .env/secret is never surfaced.
- exec-poll watcher no longer fabricates phantom create/delete storms:
  a failed poll preserves the previous snapshot, a failed/partial initial
  poll seeds without diffing (re-baselining on the first complete poll),
  and a partial (permission-denied) poll is merged, not diffed.
- watcher watches the definition workspace.root (was defaulting to
  /workspace, diverging from enrichment); native watch re-seeds if its
  initial listing fails; teardown always destroys even if stop() rejects.
- every swallowed git/exec/fs failure now logs (real anomalies under
  errors/warn, expected-empty under the sandbox debug category).
Cover the git-ignored-file withholding, the untracked-file synthesized
diff, and that swallowed failures now log; align the middleware SKILL
hook-error claim with the actual errors category.
@tombeckenham tombeckenham force-pushed the fix/sandbox-file-diff-observability branch from 63f58af to ea598c7 Compare July 9, 2026 10:39

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
packages/ai-sandbox/tests/with-sandbox-hooks.test.ts (1)

7-8: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Duplicate @tanstack/ai type imports still present — ESLint import/no-duplicates fails.

Previously flagged and not yet merged. Combine into a single import to clear the lint error.

♻️ Proposed fix
-import type { SandboxFileEvent } from '`@tanstack/ai`'
-import type { ChatMiddlewareContext } from '`@tanstack/ai`'
+import type { SandboxFileEvent, ChatMiddlewareContext } from '`@tanstack/ai`'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-sandbox/tests/with-sandbox-hooks.test.ts` around lines 7 - 8, The
test file still has duplicate type-only imports from the same module, causing
the import/no-duplicates lint error. Update the import section in
with-sandbox-hooks.test.ts by combining SandboxFileEvent and
ChatMiddlewareContext into a single `@tanstack/ai` type import, and keep the rest
of the test code unchanged.

Source: Linters/SAST tools

packages/ai-sandbox/src/file-diff.ts (1)

189-192: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

baseSha === '' path still bypasses git check-ignore — ignored-file contents can leak.

This was flagged previously and remains unresolved. When baseSha === '' (fresh git init with no commits), git check-ignore is functional, but this branch calls synthesizeAddPatch directly without the ignore gate used in the synthesizeIfUntracked path. An ignored .env created in such a repo would have its contents streamed into the diff feed.

Consider extracting the git check-ignore logic into a shared helper and applying it here before synthesizing.

🔒 Proposed fix: add ignore check to the `baseSha === ''` path
 const diff = async (): Promise<string> => {
   if (baseSha === '') {
     if (event.type === 'delete') return ''
-    return synthesizeAddPatch(relTo(root, event.path), await after())
+    const rel = relTo(root, event.path)
+    const content = await after()
+    if (content === '') return ''
+    try {
+      const ignored = await handle.process.exec(
+        `git check-ignore -q -- ${q(rel)}`,
+        { cwd: root },
+      )
+      if (ignored.exitCode === 0) {
+        logger?.sandbox('sandbox diff() withheld for git-ignored file', {
+          path: event.path,
+        })
+        return ''
+      }
+    } catch (error) {
+      logger?.warn('sandbox diff() git check-ignore failed', {
+        path: event.path,
+        error,
+      })
+    }
+    return synthesizeAddPatch(rel, content)
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-sandbox/src/file-diff.ts` around lines 189 - 192, The `baseSha
=== ''` branch in `diff` bypasses the same ignore filtering used by
`synthesizeIfUntracked`, so ignored files can still be included. Update the
`diff` function in `file-diff.ts` to run `git check-ignore` before calling
`synthesizeAddPatch` when there are no commits, ideally by extracting the
existing ignore check into a shared helper and reusing it in both paths. Ensure
the delete case still returns early, but all non-delete additions in this branch
go through the ignore gate first.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@packages/ai-sandbox/src/file-diff.ts`:
- Around line 189-192: The `baseSha === ''` branch in `diff` bypasses the same
ignore filtering used by `synthesizeIfUntracked`, so ignored files can still be
included. Update the `diff` function in `file-diff.ts` to run `git check-ignore`
before calling `synthesizeAddPatch` when there are no commits, ideally by
extracting the existing ignore check into a shared helper and reusing it in both
paths. Ensure the delete case still returns early, but all non-delete additions
in this branch go through the ignore gate first.

In `@packages/ai-sandbox/tests/with-sandbox-hooks.test.ts`:
- Around line 7-8: The test file still has duplicate type-only imports from the
same module, causing the import/no-duplicates lint error. Update the import
section in with-sandbox-hooks.test.ts by combining SandboxFileEvent and
ChatMiddlewareContext into a single `@tanstack/ai` type import, and keep the rest
of the test code unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 37861f75-cdc2-4acc-b6a5-b403cdcd8f9a

📥 Commits

Reviewing files that changed from the base of the PR and between 63f58af and ea598c7.

📒 Files selected for processing (11)
  • .changeset/sandbox-file-diff-observability.md
  • docs/config.json
  • docs/sandbox/observability.md
  • packages/ai-sandbox/src/file-diff.ts
  • packages/ai-sandbox/src/middleware.ts
  • packages/ai-sandbox/src/watch.ts
  • packages/ai-sandbox/tests/fakes.ts
  • packages/ai-sandbox/tests/file-diff.test.ts
  • packages/ai-sandbox/tests/watch.test.ts
  • packages/ai-sandbox/tests/with-sandbox-hooks.test.ts
  • packages/ai/skills/ai-core/middleware/SKILL.md
✅ Files skipped from review due to trivial changes (3)
  • docs/config.json
  • .changeset/sandbox-file-diff-observability.md
  • docs/sandbox/observability.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/ai-sandbox/tests/fakes.ts
  • packages/ai-sandbox/tests/watch.test.ts
  • packages/ai-sandbox/tests/file-diff.test.ts
  • packages/ai-sandbox/src/middleware.ts

A steady-state `find` poll throw was logged only under `sandbox` (off by
default). A *persistent* mid-run exec failure (seam wedged, `find` gone)
then leaves the watcher silently dead for the rest of the run with no
signal at default verbosity — the silent-degradation class this change
set out to eliminate.

Distinguish teardown (`controller.signal.aborted` → quiet `sandbox`) from a
genuine failure, and escalate to `warn` after 3 consecutive non-teardown
throws so a wedged watcher is observable while a single transient blip
stays quiet. Honors the observability doc's "real anomalies → errors
category (on by default)" contract on the one path that hid one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tombeckenham tombeckenham self-requested a review July 9, 2026 11:02

@tombeckenham tombeckenham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All good. Small changes only

@AlemTuzlak AlemTuzlak merged commit 1deaa29 into main Jul 10, 2026
10 checks passed
@AlemTuzlak AlemTuzlak deleted the fix/sandbox-file-diff-observability branch July 10, 2026 08:36
@github-actions github-actions Bot mentioned this pull request Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants