Skip to content

feat: Add just-bash support for WebContainer editing - #489

Merged
ilyabo merged 9 commits into
mainfrom
cursor/webcontainer-just-bash-support-666a
Mar 22, 2026
Merged

ilyabo merged 9 commits into
mainfrom
cursor/webcontainer-just-bash-support-666a

Conversation

@ilyabo

@ilyabo ilyabo commented Mar 21, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • add a WebContainerFsAdapter and executeBashCommand() path in @sqlrooms/webcontainer using just-bash
  • export a shared createWebContainerBashTool and renderer, then switch the app-builder example and CLI app to the new bash tool
  • add a browser node:zlib shim and Vite aliases so the app-builder example can bundle just-bash

Testing

  • pnpm --filter @sqlrooms/webcontainer typecheck
  • pnpm --filter @sqlrooms/webcontainer build
  • pnpm --filter app-builder-example build
  • runtime harness: exercised just-bash through WebContainerFsAdapter and verified read/edit/copy/remove operations persist back to the mocked WebContainer filesystem
  • known unrelated repo baseline failures:
    • pnpm build fails in @sqlrooms/kepler
    • pnpm --filter sqlrooms-cli-app build / pnpm --filter sqlrooms-cli-app exec vite build fail on existing @sqlrooms/cells / @sqlrooms/notebook resolution and type issues
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Run bash in WebContainer, plus a sandboxed read/write file toolkit and a filesystem adapter for WebContainer sync.
  • Bug Fixes

    • Persistence made safer: storage writes and large UI-message saves now catch and log errors instead of crashing.
  • Refactor

    • Consolidated individual file/command tools into a unified WebContainer toolkit and simplified AI/tool initialization.
  • Chores

    • Added browser shim for zlib-related APIs and updated dev build aliases.

@coderabbitai

coderabbitai Bot commented Mar 21, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Reworks WebContainer internals and AI tooling: adds FS adapter, sandbox, and a WebContainer-backed AI toolkit (bash/readFile/writeFile) with renderers; updates store wiring to inject the toolkit; changes persistence handling and Vite aliases; removes redundant example tools and adjusts example UI/config.

Changes

Cohort / File(s) Summary
WebContainer core & toolkit
packages/webcontainer/src/WebContainerFsAdapter.ts, packages/webcontainer/src/WebContainerSandbox.ts, packages/webcontainer/src/createWebContainerBashTool.tsx, packages/webcontainer/src/WebContainerSlice.ts, packages/webcontainer/src/shims/node-zlib.ts, packages/webcontainer/src/index.ts
Added WebContainerFsAdapter (in-memory mirror), Sandbox factory, createWebContainerToolkit (bash/readFile/writeFile ai tools + React renderers), executeBashCommand with output truncation and persistence sync, WebContainerPersistConfig, node:zlib shim, and re-exported these APIs.
Store wiring & persistence
apps/sqlrooms-cli-ui/src/store.ts, examples/app-builder/src/store/store.ts, packages/room-store/src/createPersistHelpers.ts
Switched persisted webContainer slice to WebContainerPersistConfig; refactored AI slice init to create and inject toolkit via createWebContainerToolkit(store) (IIFE) and composed tool/toolRenderer maps; added createSafeStorage wrapper to swallow/log persistence errors.
Example app — removed local tools
examples/app-builder/src/tools/listFiles/*, examples/app-builder/src/tools/getFileContent/*, examples/app-builder/src/tools/updateFileContent/*
Deleted local listFiles/getFileContent/updateFileContent tool implementations and their result renderers (functionality moved to centralized toolkit).
Example app — UI & config
examples/app-builder/src/components/AssistantView.tsx, examples/app-builder/src/components/Room.tsx, examples/app-builder/package.json, examples/app-builder/vite.config.ts, examples/ai-nextjs/next-env.d.ts
Wired inline API key input to room store, wrapped UI panels in TooltipProvider and added Toaster, removed several example deps, added Vite alias for node:zlib, and adjusted Next.js generated route types import path.
CLI UI Vite config
apps/sqlrooms-cli-ui/vite.config.ts
Added Vite alias mapping node:zlib to the local shim using fileURLToPath(import.meta.url).
AI slice behavior
packages/ai-core/src/AiSlice.ts
Changed setSessionUiMessages to return boolean and wrapped state update in try/catch to log and return false on error.
Package metadata
packages/webcontainer/package.json
Added runtime dependencies: ai and just-bash.

Sequence Diagram

sequenceDiagram
    participant Store as Store Init
    participant Toolkit as createWebContainerToolkit
    participant Sandbox as WebContainerSandbox
    participant FsAdapter as WebContainerFsAdapter
    participant AiSlice as createAiSlice
    participant WebContainer as WebContainer Instance

    Store->>Toolkit: createWebContainerToolkit(store)
    Toolkit->>WebContainer: read webContainer from store.getState()
    Toolkit->>FsAdapter: new WebContainerFsAdapter(webContainer)
    Toolkit->>Sandbox: createWebContainerSandbox(getState)
    Toolkit-->>Store: return { tools, toolRenderers }
    Store->>AiSlice: createAiSlice({ tools: default + toolkit.tools, toolRenderers: ... })
    AiSlice->>Toolkit: invoke toolkit tools (bash/readFile/writeFile)
    Toolkit->>Sandbox: bash/readFile/writeFile -> sandbox methods
    Sandbox->>FsAdapter: sync/read/write operations
    Sandbox->>WebContainer: execute/persist/open/update/saveAllOpenFiles
    WebContainer-->>Store: sync slice config / update persisted state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • dmitriy-kostianetskiy
  • lixun910

Poem

🐰 I hopped through lines with nimble paws,

Built sandboxes, tools, and mirrored laws,
One toolkit now sings bash and read,
Files in sync — a joyful deed,
Little rabbit dance — code shipped with applause! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: Add just-bash support for WebContainer editing' accurately summarizes the main objective of this changeset, which centers on integrating just-bash for WebContainer file operations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 cursor/webcontainer-just-bash-support-666a

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.

@ilyabo ilyabo changed the title Add just-bash support for WebContainer editing feat: Add just-bash support for WebContainer editing Mar 21, 2026
ilyabo added 3 commits March 21, 2026 17:42
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
@ilyabo
ilyabo marked this pull request as ready for review March 21, 2026 21:49

@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: 3

🧹 Nitpick comments (4)
packages/webcontainer/src/WebContainerFsAdapter.ts (1)

100-102: Type assertion bypasses compile-time interface verification.

Using as unknown as IFileSystem circumvents TypeScript's structural typing. If IFileSystem adds new required methods in just-bash, this class won't fail to compile even though it's incomplete.

Consider adding implements IFileSystem to the class declaration for compile-time guarantees:

-export class WebContainerFsAdapter {
+export class WebContainerFsAdapter implements IFileSystem {

This would require importing IFileSystem as a type and ensuring all methods match the expected signatures.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/webcontainer/src/WebContainerFsAdapter.ts` around lines 100 - 102,
The current asFileSystem() uses a double type assertion that bypasses TypeScript
checks; update the WebContainerFsAdapter class to explicitly implement the
IFileSystem interface (add "implements IFileSystem" to the class declaration and
import IFileSystem as a type), then adjust or remove asFileSystem() to return
this as a properly-typed IFileSystem (or simply return this) and ensure all
methods on WebContainerFsAdapter match the IFileSystem method signatures so the
compiler can verify completeness.
packages/webcontainer/src/shims/node-zlib.ts (1)

7-11: Consider clarifying the error message.

The message "just-bash gzip commands remain unsupported in this environment" could be confusing since it implies just-bash's gzip support is limited, when actually the issue is that Node's synchronous zlib API isn't available in the browser. The actual just-bash gzip commands may work fine through the WebContainer.

💡 Suggested clarification
 function unsupported(methodName: string): never {
   throw new Error(
-    `${methodName} is not available in browser builds. just-bash gzip commands remain unsupported in this environment.`,
+    `${methodName} is not available in browser builds. Use the WebContainer's native filesystem or just-bash commands instead.`,
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/webcontainer/src/shims/node-zlib.ts` around lines 7 - 11, The error
thrown by the unsupported function currently implies that "just-bash gzip
commands" are unsupported; update the message in unsupported(methodName: string)
to clearly state that Node's synchronous zlib APIs are not available in browser
builds and that the browser environment (WebContainer) may still support gzip
via other means, while including the methodName and the phrase "not available in
browser builds (synchronous zlib APIs unsupported)". Keep the throw in
unsupported(...) and only change the error text to this clarified wording.
packages/webcontainer/src/createWebContainerBashTool.tsx (2)

162-167: Props types are missing toolCallId expected by ToolRendererProps.

Per the ToolRendererProps interface in packages/ai-core/src/types.ts, tool renderers receive a toolCallId prop. While the current implementation works (unused props are simply ignored), adding it to the type definitions would ensure consistency with the framework's expectations.

♻️ Suggested props type alignment
 type WebContainerBashToolResultProps = {
   input: WebContainerBashToolParameters;
   output: WebContainerBashToolOutput | undefined;
   state: ToolResultState;
+  toolCallId: string;
   errorText?: string;
 };

Similarly for the other result props types.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 162 -
167, The WebContainerBashToolResultProps type is missing the toolCallId prop
expected by the ToolRendererProps interface; update the type definition for
WebContainerBashToolResultProps (and any other result props types) to include
toolCallId: string (or the correct id type) so the component signatures match
the framework's expectations, ensuring you add the field alongside input,
output, state, and errorText in the type declaration.

43-50: Consider improving type safety for WebContainerToolkitResult.

The any types here lose the benefit of TypeScript's type checking. While the eslint-disable comment indicates this is intentional, using more specific types would improve DX for consumers.

💡 Suggested type improvement
-// eslint-disable-next-line `@typescript-eslint/no-explicit-any`
-export type WebContainerToolkitResult = {
-  bash: any;
-  readFile: any;
-  writeFile: any;
-  tools: Record<string, any>;
-  toolRenderers: Record<string, React.ComponentType<any>>;
-};
+export type WebContainerToolkitResult = {
+  bash: ReturnType<typeof tool<typeof WebContainerBashToolParameters, WebContainerBashToolOutput>>;
+  readFile: ReturnType<typeof tool>;
+  writeFile: ReturnType<typeof tool>;
+  tools: {
+    bash: WebContainerToolkitResult['bash'];
+    readFile: WebContainerToolkitResult['readFile'];
+    writeFile: WebContainerToolkitResult['writeFile'];
+  };
+  toolRenderers: Record<string, React.ComponentType<ToolRendererProps<unknown, unknown>>>;
+};

Alternatively, if complex inference is problematic, at minimum the tools and toolRenderers keys could be typed as string literals:

tools: Record<'bash' | 'readFile' | 'writeFile', Tool>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 43 -
50, The WebContainerToolkitResult type uses broad any types (bash, readFile,
writeFile, tools, toolRenderers) which reduces TypeScript safety; define
explicit interfaces (e.g., a Tool type for tool functions/objects and a
ToolRenderer type for React components) and replace the anys with those
types—assign bash a callable signature (promise-returning), readFile/writeFile
appropriate param/return types, tools as Record<string, Tool> (or a union of
literal keys like 'bash'|'readFile'|'writeFile' -> Tool), and toolRenderers as
Record<string, ToolRenderer>; remove the eslint-disable comment after typing so
WebContainerToolkitResult, bash, readFile, writeFile, tools, and toolRenderers
are strongly typed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/ai-core/src/AiSlice.ts`:
- Around line 694-712: The setSessionUiMessages setter swallows all errors and
logs a misleading "payload too large" message which causes callers (e.g.,
useSessionChat and chatTransport.ts) to be unaware of persistence failures;
update setSessionUiMessages to surface failures by changing its signature to
return a boolean (or accept an optional onError callback) so callers can react:
inside setSessionUiMessages (the function currently doing
JSON.parse(JSON.stringify(uiMessages))) catch the error, log the full error
object/message (not a fixed payload message), invoke the onError callback if
provided (or return false) and only return true on success; update callers
(useSessionChat and chatTransport.ts) to check the returned success value (or
pass onError) and handle retry/notification/local state accordingly.

In `@packages/webcontainer/src/WebContainerFsAdapter.ts`:
- Around line 159-172: The mirror is mutated before the WebContainer, so if
webContainer.fs operations (e.g., webContainer.fs.writeFile) fail the mirror
becomes inconsistent; update the adapter to perform the WebContainer operation
first and only update the in-memory mirror on success (or, alternatively, catch
WebContainer errors and roll back the mirror change or re-sync from
WebContainer). Apply this change for writeFile and the same pattern to
appendFile, mkdir, rm, cp, and mv; use resolveWithinRoot and
ensureParentDirectory as needed but ensure mirror.write* calls occur only after
webContainer.fs.* completes successfully (or are reverted on error).

In `@packages/webcontainer/src/WebContainerSandbox.ts`:
- Around line 44-54: writeFiles currently calls webContainer.updateFileContent
which only mutates filesTree for new files and then saveAllOpenFiles only
persists openedFiles, so newly created files never get written to instance.fs.
Fix writeFiles in WebContainerSandbox by detecting when a file path is not
already opened (use webContainer.openedFiles or a helper on
getState().webContainer) and either call webContainer.openFile(path) before
updateFileContent or bypass updateFileContent for new files and write directly
to webContainer.instance.fs.writeFile(path, content) (then still call
saveAllOpenFiles for existing opened files). Ensure you reference the existing
methods updateFileContent, saveAllOpenFiles, openFile and the instance.fs write
API on the webContainer object so new files persist.

---

Nitpick comments:
In `@packages/webcontainer/src/createWebContainerBashTool.tsx`:
- Around line 162-167: The WebContainerBashToolResultProps type is missing the
toolCallId prop expected by the ToolRendererProps interface; update the type
definition for WebContainerBashToolResultProps (and any other result props
types) to include toolCallId: string (or the correct id type) so the component
signatures match the framework's expectations, ensuring you add the field
alongside input, output, state, and errorText in the type declaration.
- Around line 43-50: The WebContainerToolkitResult type uses broad any types
(bash, readFile, writeFile, tools, toolRenderers) which reduces TypeScript
safety; define explicit interfaces (e.g., a Tool type for tool functions/objects
and a ToolRenderer type for React components) and replace the anys with those
types—assign bash a callable signature (promise-returning), readFile/writeFile
appropriate param/return types, tools as Record<string, Tool> (or a union of
literal keys like 'bash'|'readFile'|'writeFile' -> Tool), and toolRenderers as
Record<string, ToolRenderer>; remove the eslint-disable comment after typing so
WebContainerToolkitResult, bash, readFile, writeFile, tools, and toolRenderers
are strongly typed.

In `@packages/webcontainer/src/shims/node-zlib.ts`:
- Around line 7-11: The error thrown by the unsupported function currently
implies that "just-bash gzip commands" are unsupported; update the message in
unsupported(methodName: string) to clearly state that Node's synchronous zlib
APIs are not available in browser builds and that the browser environment
(WebContainer) may still support gzip via other means, while including the
methodName and the phrase "not available in browser builds (synchronous zlib
APIs unsupported)". Keep the throw in unsupported(...) and only change the error
text to this clarified wording.

In `@packages/webcontainer/src/WebContainerFsAdapter.ts`:
- Around line 100-102: The current asFileSystem() uses a double type assertion
that bypasses TypeScript checks; update the WebContainerFsAdapter class to
explicitly implement the IFileSystem interface (add "implements IFileSystem" to
the class declaration and import IFileSystem as a type), then adjust or remove
asFileSystem() to return this as a properly-typed IFileSystem (or simply return
this) and ensure all methods on WebContainerFsAdapter match the IFileSystem
method signatures so the compiler can verify completeness.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e5b07864-8c79-4f88-b221-13442f17ef85

📥 Commits

Reviewing files that changed from the base of the PR and between f9fc960 and 76def98.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (23)
  • apps/sqlrooms-cli-ui/src/store.ts
  • apps/sqlrooms-cli-ui/vite.config.ts
  • examples/ai-nextjs/next-env.d.ts
  • examples/app-builder/package.json
  • examples/app-builder/src/components/AssistantView.tsx
  • examples/app-builder/src/components/Room.tsx
  • examples/app-builder/src/store/store.ts
  • examples/app-builder/src/tools/getFileContent/GetFileContentToolResult.tsx
  • examples/app-builder/src/tools/getFileContent/getFileContentTool.ts
  • examples/app-builder/src/tools/listFiles/ListFilesToolResult.tsx
  • examples/app-builder/src/tools/listFiles/createListFilesTool.ts
  • examples/app-builder/src/tools/updateFileContent/UpdateFileContentToolResult.tsx
  • examples/app-builder/src/tools/updateFileContent/updateFileContentTool.ts
  • examples/app-builder/vite.config.ts
  • packages/ai-core/src/AiSlice.ts
  • packages/room-store/src/createPersistHelpers.ts
  • packages/webcontainer/package.json
  • packages/webcontainer/src/WebContainerFsAdapter.ts
  • packages/webcontainer/src/WebContainerSandbox.ts
  • packages/webcontainer/src/WebContainerSlice.ts
  • packages/webcontainer/src/createWebContainerBashTool.tsx
  • packages/webcontainer/src/index.ts
  • packages/webcontainer/src/shims/node-zlib.ts
💤 Files with no reviewable changes (6)
  • examples/app-builder/src/tools/listFiles/ListFilesToolResult.tsx
  • examples/app-builder/src/tools/updateFileContent/UpdateFileContentToolResult.tsx
  • examples/app-builder/src/tools/getFileContent/GetFileContentToolResult.tsx
  • examples/app-builder/src/tools/listFiles/createListFilesTool.ts
  • examples/app-builder/src/tools/updateFileContent/updateFileContentTool.ts
  • examples/app-builder/src/tools/getFileContent/getFileContentTool.ts

Comment thread packages/ai-core/src/AiSlice.ts Outdated
Comment thread packages/webcontainer/src/WebContainerSandbox.ts
Signed-off-by: Ilya Boyandin <[email protected]>

@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

♻️ Duplicate comments (1)
packages/webcontainer/src/WebContainerFsAdapter.ts (1)

174-193: ⚠️ Potential issue | 🟠 Major

Broaden the append rollback to cover parent creation.

The try block only starts after Line 185. If webContainer.fs.mkdir() fails there, the mirror keeps the appended bytes while the WebContainer stays unchanged. The existing resync path should start before ensureParentDirectory() as well.

🛠️ Suggested fix
   async appendFile(
     path: string,
     content: FileContent,
     options?: {encoding?: BufferEncoding} | BufferEncoding,
   ): Promise<void> {
     const resolvedPath = this.resolveWithinRoot('/', path);
     await this.mirror.appendFile(
       resolvedPath,
       content,
       options as BufferEncoding,
     );
-    await this.ensureParentDirectory(resolvedPath);
-    const updatedContents = await this.mirror.readFileBuffer(resolvedPath);
     try {
+      await this.ensureParentDirectory(resolvedPath);
+      const updatedContents = await this.mirror.readFileBuffer(resolvedPath);
       await this.webContainer.fs.writeFile(resolvedPath, updatedContents);
     } catch (error) {
       await this.syncFromWebContainer();
       throw error;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/webcontainer/src/WebContainerFsAdapter.ts` around lines 174 - 193,
In appendFile, broaden the try/catch to start before calling
ensureParentDirectory so any failure during parent-directory creation or the
subsequent write is rolled back; specifically, wrap the mirror.appendFile and
ensureParentDirectory and the webContainer.fs.writeFile calls inside the same
try block (referencing appendFile, mirror.appendFile, ensureParentDirectory,
webContainer.fs.writeFile) and retain the catch behavior to call
this.syncFromWebContainer() and rethrow the error.
🧹 Nitpick comments (4)
packages/webcontainer/src/createWebContainerBashTool.tsx (4)

52-61: Consider extracting Zod schemas for readFile and writeFile parameters for type consistency.

The bash tool has its parameter schema exported (WebContainerBashToolParameters) with an inferred type, but readFile and writeFile use inline object types in WebContainerToolkitResult while the actual tool definitions use inline Zod schemas (lines 116-118, 128-131). Extracting these schemas would provide consistent typing and allow consumers to reuse the parameter types.

♻️ Suggested refactor for consistent schema exports
+export const WebContainerReadFileToolParameters = z.object({
+  path: z.string().describe('The path to the file to read'),
+});
+export type WebContainerReadFileToolParameters = z.infer<
+  typeof WebContainerReadFileToolParameters
+>;
+
+export const WebContainerWriteFileToolParameters = z.object({
+  path: z.string().describe('The path where the file should be written'),
+  content: z.string().describe('The content to write to the file'),
+});
+export type WebContainerWriteFileToolParameters = z.infer<
+  typeof WebContainerWriteFileToolParameters
+>;
+
 export type WebContainerToolkitResult = {
   bash: Tool<WebContainerBashToolParameters, WebContainerBashToolOutput>;
-  readFile: Tool<{path: string}, WebContainerReadFileToolOutput>;
-  writeFile: Tool<
-    {path: string; content: string},
-    WebContainerWriteFileToolOutput
-  >;
+  readFile: Tool<WebContainerReadFileToolParameters, WebContainerReadFileToolOutput>;
+  writeFile: Tool<WebContainerWriteFileToolParameters, WebContainerWriteFileToolOutput>;
   tools: Record<string, Tool>;
   toolRenderers: Record<string, ComponentType<ToolResultRendererProps>>;
 };

Then reference these schemas in the tool definitions at lines 116-118 and 128-131.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 52 -
61, Extract and export Zod schemas and inferred TS types for the readFile and
writeFile tool parameters (e.g., create WebContainerReadFileParamsSchema and
WebContainerWriteFileParamsSchema and export types like
WebContainerReadFileParameters and WebContainerWriteFileParameters), replace the
inline object types used in the WebContainerToolkitResult type and the
readFile/writeFile tool definitions with these exported parameter types, and
update any references to use the new schema variables where the tools are
defined (matching how WebContainerBashToolParameters is used) to ensure
consistent typing and reuse.

218-226: Unused destructured props may trigger lint warnings.

The output and state props are declared in the type but not used. The readFile renderer doesn't display success/error states unlike the bash renderer (which shows exit code and error text). Consider either:

  1. Prefix unused props with underscore to satisfy linting
  2. Enhance the renderer to show loading/success/error states for consistency
♻️ Option 1: Prefix unused props
 export function WebContainerReadFileToolResult({
   input,
+  state: _state,
+  output: _output,
 }: WebContainerReadFileToolResultProps) {
♻️ Option 2: Show state feedback
 export function WebContainerReadFileToolResult({
   input,
+  state,
+  output,
 }: WebContainerReadFileToolResultProps) {
+  const isFinished = state === 'output-available' || state === 'output-error';
+  const suffix = isFinished
+    ? output
+      ? ` (${output.content.length} bytes)`
+      : ' (failed)'
+    : '';
+
   return (
     <div className="text-foreground/50 text-xs">
-      Reading file <span className="font-mono">{input.path}</span>
+      Reading file <span className="font-mono">{input.path}</span>{suffix}
     </div>
   );
 }

As per coding guidelines: "Maintain lint baseline with warnings only; zero errors is the expected state."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 218 -
226, The component WebContainerReadFileToolResult currently destructures props
declared on WebContainerReadFileToolResultProps but does not use output and
state, causing lint warnings; either (A) rename the unused props to _output and
_state in the WebContainerReadFileToolResult signature to satisfy linting, or
(B) enhance WebContainerReadFileToolResult to render state feedback consistent
with the bash renderer: use state to show loading indicator, display output
(file contents) on success, and render error text/exit code on failure
(reference the output and state properties on
WebContainerReadFileToolResultProps when implementing this).

235-242: Same unused props concern applies to WebContainerWriteFileToolResult.

Similar to WebContainerReadFileToolResult, the output and state props are unused and the renderer doesn't show operation status. The same two options (prefix unused or enhance renderer) apply here.

♻️ Suggested enhancement for write file renderer
 export function WebContainerWriteFileToolResult({
   input,
+  state,
 }: WebContainerWriteFileToolResultProps) {
+  const isFinished = state === 'output-available' || state === 'output-error';
+  const suffix = state === 'output-error'
+    ? ' (failed)'
+    : isFinished
+      ? ' (done)'
+      : '';
+
   return (
     <div className="text-foreground/50 text-xs">
-      Writing file <span className="font-mono">{input.path}</span>
+      Writing file <span className="font-mono">{input.path}</span>{suffix}
     </div>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 235 -
242, The WebContainerWriteFileToolResult component declares props (output,
state, input) but only uses input.path; either remove the unused props from
WebContainerWriteFileToolResultProps and the component signature to avoid lint
warnings, or enhance the renderer to display operation status by using the state
and output props (e.g., show "Writing...", "Success" / "Error" and any message
from output) — update the WebContainerWriteFileToolResult function signature and
JSX accordingly to either drop output/state references or render their values
alongside input.path.

114-139: Inconsistent error handling between tools—consider returning structured errors instead of throwing.

The bash tool returns errors within a structured response ({success, details, errorMessage?}), but readFile and writeFile throw exceptions. While the Vercel AI SDK does handle thrown errors as 'tool-error' parts in tool execution, the inconsistency makes the API harder to use. Align readFile and writeFile with the bash tool pattern by returning structured error responses instead of throwing, which also better matches SDK best practices of returning meaningful error objects.

♻️ Suggested error handling for file tools
   const readFile = tool({
     description: 'Read the contents of a file from the project',
     inputSchema: z.object({
       path: z.string().describe('The path to the file to read'),
     }),
     execute: async ({path}) => {
+      try {
         const content = await getSandbox().readFile(path);
         return {content} satisfies WebContainerReadFileToolOutput;
+      } catch (error) {
+        throw new Error(
+          `Failed to read file "${path}": ${error instanceof Error ? error.message : String(error)}`
+        );
+      }
     },
   });
 
   const writeFile = tool({
     description:
       'Write content to a file in the project. Creates the file if it does not exist.',
     inputSchema: z.object({
       path: z.string().describe('The path where the file should be written'),
       content: z.string().describe('The content to write to the file'),
     }),
     execute: async ({
       path,
       content,
     }): Promise<WebContainerWriteFileToolOutput> => {
+      try {
         await getSandbox().writeFiles([{path, content}]);
         return {success: true};
+      } catch (error) {
+        throw new Error(
+          `Failed to write file "${path}": ${error instanceof Error ? error.message : String(error)}`
+        );
+      }
     },
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 114 -
139, The readFile and writeFile tool execute handlers currently throw on
failure; change them to mirror the bash tool's structured response pattern by
catching errors from getSandbox().readFile and getSandbox().writeFiles and
returning typed objects (e.g., WebContainerReadFileToolOutput and
WebContainerWriteFileToolOutput) with success: false plus errorMessage and
optional details instead of throwing; when successful return success: true and
content (for readFile) or success: true (for writeFile), and preserve the
existing inputSchema and types so callers receive consistent structured error
responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/webcontainer/src/WebContainerFsAdapter.ts`:
- Around line 159-172: The adapter's writeFile method drops the encoding when
calling this.webContainer.fs.writeFile, causing divergence with
this.mirror.writeFile when non-UTF8 encodings are used; update writeFile to
forward the encoding from the options parameter (handle both options object and
BufferEncoding overload) into the call to this.webContainer.fs.writeFile so both
the webContainer and mirror receive the same encoding; locate the writeFile
function and pass the resolved encoding (derived from options or cast as
BufferEncoding) into webContainer.fs.writeFile as the third argument.
- Around line 257-260: The chmod and utimes implementations in
WebContainerFsAdapter currently only mutate this.mirror (resolveWithinRoot +
this.mirror.chmod/utimes) which loses metadata on sync; change both chmod(path:
string, mode: number) and utimes(path: string, atime: number, mtime: number) to
throw the same unsupported-operation error used by symlink() and link() (include
a clear message like "chmod/utimes not supported by WebContainer adapter")
instead of mutating the mirror, so they fail fast and avoid silent metadata
loss.

---

Duplicate comments:
In `@packages/webcontainer/src/WebContainerFsAdapter.ts`:
- Around line 174-193: In appendFile, broaden the try/catch to start before
calling ensureParentDirectory so any failure during parent-directory creation or
the subsequent write is rolled back; specifically, wrap the mirror.appendFile
and ensureParentDirectory and the webContainer.fs.writeFile calls inside the
same try block (referencing appendFile, mirror.appendFile,
ensureParentDirectory, webContainer.fs.writeFile) and retain the catch behavior
to call this.syncFromWebContainer() and rethrow the error.

---

Nitpick comments:
In `@packages/webcontainer/src/createWebContainerBashTool.tsx`:
- Around line 52-61: Extract and export Zod schemas and inferred TS types for
the readFile and writeFile tool parameters (e.g., create
WebContainerReadFileParamsSchema and WebContainerWriteFileParamsSchema and
export types like WebContainerReadFileParameters and
WebContainerWriteFileParameters), replace the inline object types used in the
WebContainerToolkitResult type and the readFile/writeFile tool definitions with
these exported parameter types, and update any references to use the new schema
variables where the tools are defined (matching how
WebContainerBashToolParameters is used) to ensure consistent typing and reuse.
- Around line 218-226: The component WebContainerReadFileToolResult currently
destructures props declared on WebContainerReadFileToolResultProps but does not
use output and state, causing lint warnings; either (A) rename the unused props
to _output and _state in the WebContainerReadFileToolResult signature to satisfy
linting, or (B) enhance WebContainerReadFileToolResult to render state feedback
consistent with the bash renderer: use state to show loading indicator, display
output (file contents) on success, and render error text/exit code on failure
(reference the output and state properties on
WebContainerReadFileToolResultProps when implementing this).
- Around line 235-242: The WebContainerWriteFileToolResult component declares
props (output, state, input) but only uses input.path; either remove the unused
props from WebContainerWriteFileToolResultProps and the component signature to
avoid lint warnings, or enhance the renderer to display operation status by
using the state and output props (e.g., show "Writing...", "Success" / "Error"
and any message from output) — update the WebContainerWriteFileToolResult
function signature and JSX accordingly to either drop output/state references or
render their values alongside input.path.
- Around line 114-139: The readFile and writeFile tool execute handlers
currently throw on failure; change them to mirror the bash tool's structured
response pattern by catching errors from getSandbox().readFile and
getSandbox().writeFiles and returning typed objects (e.g.,
WebContainerReadFileToolOutput and WebContainerWriteFileToolOutput) with
success: false plus errorMessage and optional details instead of throwing; when
successful return success: true and content (for readFile) or success: true (for
writeFile), and preserve the existing inputSchema and types so callers receive
consistent structured error responses.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 50456dcf-241f-4cba-b8de-342ca111dd1b

📥 Commits

Reviewing files that changed from the base of the PR and between 76def98 and 6f005ac.

📒 Files selected for processing (5)
  • packages/ai-core/src/AiSlice.ts
  • packages/webcontainer/src/WebContainerFsAdapter.ts
  • packages/webcontainer/src/WebContainerSandbox.ts
  • packages/webcontainer/src/createWebContainerBashTool.tsx
  • packages/webcontainer/src/shims/node-zlib.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/webcontainer/src/shims/node-zlib.ts
  • packages/ai-core/src/AiSlice.ts
  • packages/webcontainer/src/WebContainerSandbox.ts

Comment thread packages/webcontainer/src/WebContainerFsAdapter.ts
Comment on lines +257 to +260
async chmod(path: string, mode: number): Promise<void> {
const resolvedPath = this.resolveWithinRoot('/', path);
await this.mirror.chmod(resolvedPath, mode);
}

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the WebContainerFsAdapter file
find . -name "WebContainerFsAdapter.ts" -type f

Repository: sqlrooms/sqlrooms

Length of output: 113


🏁 Script executed:

# Read the WebContainerFsAdapter.ts file
wc -l ./packages/webcontainer/src/WebContainerFsAdapter.ts

Repository: sqlrooms/sqlrooms

Length of output: 117


🏁 Script executed:

# Read the entire WebContainerFsAdapter.ts file
cat -n ./packages/webcontainer/src/WebContainerFsAdapter.ts

Repository: sqlrooms/sqlrooms

Length of output: 10953


🏁 Script executed:

# Check if there are type definitions or API docs mentioning chmod/utimes in WebContainer
rg "chmod|utimes" --type ts --type tsx -i

Repository: sqlrooms/sqlrooms

Length of output: 88


🏁 Script executed:

# Check for chmod/utimes references in TypeScript files
rg "chmod|utimes" --type ts -i

Repository: sqlrooms/sqlrooms

Length of output: 502


🏁 Script executed:

# Check the WebContainer type definition to see what fs methods are available
rg "@webcontainer/api" --type ts -A 3 -B 3

Repository: sqlrooms/sqlrooms

Length of output: 3520


🏁 Script executed:

# Look for package.json to see `@webcontainer/api` version
find . -name "package.json" -type f | head -5 | xargs grep -l "@webcontainer/api"

Repository: sqlrooms/sqlrooms

Length of output: 96


🏁 Script executed:

# Check the WebContainer API package.json
cat ./apps/sqlrooms-cli-ui/package.json | grep -A 2 -B 2 "@webcontainer"

Repository: sqlrooms/sqlrooms

Length of output: 233


🏁 Script executed:

# Check just-bash import to understand the InMemoryFs interface
rg "InMemoryFs" --type ts -B 5 -A 10

Repository: sqlrooms/sqlrooms

Length of output: 4292


🏁 Script executed:

# Search for any tests or usage of chmod/utimes in the codebase
rg "chmod|utimes" -i --type ts

Repository: sqlrooms/sqlrooms

Length of output: 502


🏁 Script executed:

# Check what methods are called on webContainer.fs throughout the codebase
rg "this\.webContainer\.fs\." --type ts

Repository: sqlrooms/sqlrooms

Length of output: 1098


🏁 Script executed:

# Check the WebContainer type to understand what methods are available
rg "type WebContainerLike" --type ts -A 5

Repository: sqlrooms/sqlrooms

Length of output: 526


🏁 Script executed:

# Check package.json files to see all dependencies and their versions
find . -name "package.json" -path "*/webcontainer/*" -exec cat {} \;

Repository: sqlrooms/sqlrooms

Length of output: 1039


🏁 Script executed:

# Search for any usage of the WebContainerFsAdapter class
rg "WebContainerFsAdapter" --type ts

Repository: sqlrooms/sqlrooms

Length of output: 655


🏁 Script executed:

# Quick check to see if there are any tests for WebContainerFsAdapter
find . -name "*.test.ts" -o -name "*.spec.ts" | xargs rg -l "WebContainerFsAdapter|chmod|utimes" 2>/dev/null

Repository: sqlrooms/sqlrooms

Length of output: 45


🏁 Script executed:

# Look for test files
find . -name "*.test.ts" -o -name "*.spec.ts" | head -10

Repository: sqlrooms/sqlrooms

Length of output: 641


Throw on chmod/utimes to prevent silent metadata loss.

Lines 257-260 and 289-292 only update this.mirror without persisting to WebContainer. Since syncFromWebContainer() replaces the entire mirror with a fresh export, these metadata changes disappear silently on the next sync. Meanwhile, symlink() and link() explicitly throw for unsupported operations, and other methods like writeFile(), mkdir(), and rm() persist to both WebContainer and mirror. This inconsistency creates a data loss hazard.

Either implement real persistence for chmod/utimes or throw, matching the pattern of symlink() and link().

🛠️ Suggested fix
   async chmod(_path: string, _mode: number): Promise<void> {
-    const resolvedPath = this.resolveWithinRoot('/', path);
-    await this.mirror.chmod(resolvedPath, mode);
+    throw new Error(
+      'WebContainer filesystem adapter does not support persisting chmod changes.',
+    );
   }
@@
   async utimes(path: string, atime: Date, mtime: Date): Promise<void> {
-    const resolvedPath = this.resolveWithinRoot('/', path);
-    await this.mirror.utimes(resolvedPath, atime, mtime);
+    throw new Error(
+      'WebContainer filesystem adapter does not support persisting timestamp changes.',
+    );
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/webcontainer/src/WebContainerFsAdapter.ts` around lines 257 - 260,
The chmod and utimes implementations in WebContainerFsAdapter currently only
mutate this.mirror (resolveWithinRoot + this.mirror.chmod/utimes) which loses
metadata on sync; change both chmod(path: string, mode: number) and utimes(path:
string, atime: number, mtime: number) to throw the same unsupported-operation
error used by symlink() and link() (include a clear message like "chmod/utimes
not supported by WebContainer adapter") instead of mutating the mirror, so they
fail fast and avoid silent metadata loss.

Signed-off-by: Ilya Boyandin <[email protected]>

@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.

🧹 Nitpick comments (2)
packages/webcontainer/src/createWebContainerBashTool.tsx (2)

62-68: Minor: ToolResultState is referenced before its declaration.

The type ToolResultState used on line 66 is defined later at line 204. While TypeScript hoists type declarations so this compiles correctly, consider moving ToolResultState definition earlier (near line 62) for improved readability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 62 -
68, ToolResultRendererProps references the ToolResultState type before its
declaration; move the ToolResultState type definition so it appears above (or
immediately before) the ToolResultRendererProps definition, or alternatively
inline/export it near the top of this file so ToolResultRendererProps references
a previously declared symbol — update the declaration order around
ToolResultState and ToolResultRendererProps to improve readability.

115-132: Inconsistent error handling: bash tool lacks try-catch wrapper.

The readFile and writeFile tools wrap their execute handlers in try-catch to return structured error responses. However, bash does not, so if getSandbox().executeCommand(command) throws (e.g., sandbox initialization failure), the exception propagates instead of returning { success: false, errorMessage: ... }.

♻️ Proposed fix for consistent error handling
     execute: async ({command}) => {
+      try {
         const result = await getSandbox().executeCommand(command);
         return {
           success: result.exitCode === 0,
           details: {
             command,
             stdout: result.stdout,
             stderr: result.stderr,
             exitCode: result.exitCode,
           },
           ...(result.exitCode === 0
             ? {}
             : {
                 errorMessage:
                   result.stderr || `Command exited with code ${result.exitCode}.`,
               }),
         } satisfies WebContainerBashToolOutput;
+      } catch (error) {
+        return {
+          success: false,
+          details: {
+            command,
+            stdout: '',
+            stderr: '',
+            exitCode: -1,
+          },
+          errorMessage:
+            error instanceof Error ? error.message : 'Failed to execute command',
+        } satisfies WebContainerBashToolOutput;
+      }
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/webcontainer/src/createWebContainerBashTool.tsx` around lines 115 -
132, The bash tool's execute handler (the async execute in
createWebContainerBashTool) doesn't catch exceptions from
getSandbox().executeCommand(command), so thrown errors propagate instead of
returning a structured WebContainerBashToolOutput; wrap the body of that execute
function in a try-catch, call getSandbox().executeCommand(command) inside try,
construct the success/details/errorMessage shape as currently done on nonzero
exitCode, and in catch return { success: false, errorMessage: error.message ||
String(error), details: { command } } to match the other tools' error-response
pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/webcontainer/src/createWebContainerBashTool.tsx`:
- Around line 62-68: ToolResultRendererProps references the ToolResultState type
before its declaration; move the ToolResultState type definition so it appears
above (or immediately before) the ToolResultRendererProps definition, or
alternatively inline/export it near the top of this file so
ToolResultRendererProps references a previously declared symbol — update the
declaration order around ToolResultState and ToolResultRendererProps to improve
readability.
- Around line 115-132: The bash tool's execute handler (the async execute in
createWebContainerBashTool) doesn't catch exceptions from
getSandbox().executeCommand(command), so thrown errors propagate instead of
returning a structured WebContainerBashToolOutput; wrap the body of that execute
function in a try-catch, call getSandbox().executeCommand(command) inside try,
construct the success/details/errorMessage shape as currently done on nonzero
exitCode, and in catch return { success: false, errorMessage: error.message ||
String(error), details: { command } } to match the other tools' error-response
pattern.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c873fb43-db70-4f37-8525-97965ab3faf1

📥 Commits

Reviewing files that changed from the base of the PR and between 6f005ac and b5ffe61.

📒 Files selected for processing (3)
  • packages/webcontainer/src/WebContainerFsAdapter.ts
  • packages/webcontainer/src/createWebContainerBashTool.tsx
  • packages/webcontainer/src/index.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/webcontainer/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/webcontainer/src/WebContainerFsAdapter.ts

@ilyabo
ilyabo merged commit 7fbecb7 into main Mar 22, 2026
5 checks passed
@ilyabo
ilyabo deleted the cursor/webcontainer-just-bash-support-666a branch March 22, 2026 08:37
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