Skip to content

fix: sandbox enable toggle and Linux status detection - #293

Open
seannair wants to merge 3 commits into
OpenCoworkAI:mainfrom
seannair:fix/sandbox-enable-toggle
Open

fix: sandbox enable toggle and Linux status detection#293
seannair wants to merge 3 commits into
OpenCoworkAI:mainfrom
seannair:fix/sandbox-enable-toggle

Conversation

@seannair

Copy link
Copy Markdown

Summary

Two related but separate fixes to the Sandbox settings panel (SettingsSandbox.tsx):

  1. Sandbox enable toggle was missing entirely. The panel displayed status correctly (WSL2/Lima detection, Node/Python/pip checks all worked), but there was no way to actually turn Sandbox Mode on or off — handleToggleSandbox existed only as a commented-out // TODO: Re-enable when sandbox debugging is complete stub, with no UI control wired to it at all. Backend support (config.save IPC handler → configStore.updatesyncConfigAfterMutationsessionManager.reloadSandbox()) was already fully implemented and required no changes. This affects all platforms (Windows/WSL2, macOS/Lima, Linux), since it's the same shared component.

  2. sandboxReady/sandboxAvailable were hardcoded to false on native Linux hosts. Separately, while testing the toggle fix by running the dev build directly on Linux (as opposed to Windows using WSL2 as a backend — a different code path), the status always read "Sandbox enabled but not fully configured" even though Linux native mode has no further setup step (per the existing sandbox.linuxNative copy: "Linux runs commands natively without additional sandboxing"). Fixed by treating Linux as always available/ready, matching intended behavior — this does not affect the Windows/WSL2 or macOS/Lima detection logic, which were already correct.

Related

Testing

  • Verified on Windows 11 + WSL2 (Ubuntu): toggle now switches Sandbox Mode on/off, status correctly reports "Sandbox ready and running" with WSL2/Node/Python/pip all green.
  • Verified on native Linux (WSL Ubuntu run directly, non-Windows code path): status now correctly reports ready instead of a permanent false warning.
  • tsc --noEmit and eslint pass with no new errors.

Implements the missing sandbox toggle handler and UI switch (all platforms).

Also fixes sandboxReady/sandboxAvailable being hardcoded false on native Linux hosts.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review mode: initial

Findings

  • [Minor] Success message timeout race condition
    When the user toggles sandbox quickly within 3 seconds, the previous setTimeout(() => setSuccess(null), 3000) may fire after the new success message is set, prematurely clearing it. This happens because the timeout is not cleared before setting a new one.
    Suggested fix: Store the timeout ID in a ref and clear it in the cleanup logic of handleToggleSandbox.

    const successTimeoutRef = useRef<NodeJS.Timeout>();
    // In handleToggleSandbox, before setting new timeout:
    if (successTimeoutRef.current) clearTimeout(successTimeoutRef.current);
    successTimeoutRef.current = setTimeout(() => setSuccess(null), 3000);
  • [Nit] Missing guard for non-Electron environments
    handleToggleSandbox assumes window.electronAPI.config.save exists. If the component were ever rendered outside Electron (e.g., in a browser), clicking the toggle would throw. Consider adding a guard similar to the initialization effect.
    Suggested fix: Add early return if !isElectron.

    if (!isElectron) return;

Questions

  • The PR description mentions fixing sandboxReady/sandboxAvailable being hardcoded to false on native Linux, but no such change appears in the diff. Was this fix already in the base branch, or is it intended for a separate PR?

Summary

  • The toggle implementation is generally correct: optimistic UI disabled during saving, error handling, and accessibility attributes are properly applied.
  • The 500ms delay for status reload is reasonable but may need adjustment if the backend takes longer in practice.
  • The only significant issue is the success message race condition; the other items are minor or questions.
  • Testing: Manual verification on Windows and Linux was described. Automated tests are not present in the diff; consider adding unit tests for handleToggleSandbox (e.g., mocking window.electronAPI).

Testing

  • Not run (automation)

Open Cowork Bot

Adds isElectron guard and clears prior timeout before setting a new success timeout.

Also includes the Linux sandboxReady/sandboxAvailable fix missing from the prior push.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review mode: follow-up after new commits

Findings

  • [Major] Inverted toggle button label
    The button label uses {sandboxEnabled ? t('common.enable') : t('common.disable')}. When sandbox is enabled, it shows "enable" (verb), which suggests a click would enable it again, but the actual action toggles off. It should show the opposite action: {sandboxEnabled ? t('common.disable') : t('common.enable')}.
    src/renderer/components/settings/SettingsSandbox.tsx:364
    Suggested fix:

    {sandboxEnabled ? t('common.disable') : t('common.enable')}
  • [Minor] Uncleanup setTimeout for status reload
    In handleToggleSandbox, a setTimeout with 500ms delay calls loadStatus() but the timeout ID is never captured or cleared. If the component unmounts before the timeout fires (e.g., navigating away from settings), the callback will attempt to update state on an unmounted component, causing a React warning. Store the timeout in a ref and clear it on cleanup using useEffect or before subsequent toggles.
    src/renderer/components/settings/SettingsSandbox.tsx:126
    Suggested fix:

    // Add a ref next to successTimeoutRef
    const reloadTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
    // In handleToggleSandbox after success
    if (reloadTimeoutRef.current) clearTimeout(reloadTimeoutRef.current);
    reloadTimeoutRef.current = setTimeout(async () => {
      await loadStatus();
    }, 500);
    // In a useEffect cleanup for unmount
    useEffect(() => {
      return () => {
        if (reloadTimeoutRef.current) clearTimeout(reloadTimeoutRef.current);
      };
    }, []);
  • [Minor] Linux sandbox status assumed always true even when backend fails
    Setting sandboxAvailable and sandboxReady to true unconditionally for Linux ignores the possibility that the backend itself is broken or not initialized (e.g., status is null). Previously false was too restrictive; true is overly optimistic. Consider checking if status exists and if the backend reported an error, or treat as ready only when status?.initialized is true.
    src/renderer/components/settings/SettingsSandbox.tsx:314,320
    Suggested fix:

    : status?.initialized === true; // Linux: ready only if backend reports initialized

Previous feedback resolved

  • All prior findings have been addressed: success message timeout race condition is fixed with ref and cleanup, and guard for non-Electron environments is added.

Testing

  • Not run (automation)

Open Cowork Bot

Removes redundant enable/disable label (no precedent in SettingsLogs.tsx).

Clears prior reload timeout before setting a new one, and on unmount.

Linux ready/available now checks backend status.initialized instead of a hardcoded true.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review mode: follow-up after new commits

Findings

All previously flagged issues from the prior review have been satisfactorily addressed:

  • The inverted toggle button text has been resolved (the text label displaying the action was removed; the switch alone with aria-label is sufficient).
  • The reloadTimeoutRef is now captured, cleared before reuse, and cleaned up on unmount via a dedicated useEffect.
  • The Linux sandbox availability and readiness now check status?.initialized === true instead of being hardcoded to true, aligning with the backend adapter's actual initialization state.

No new issues introduced.

Residual Risks

  • The loadStatus() call inside the 500ms timeout does not protect against setting state after component unmount if the async operation completes after cleanup. However, the timeout is cleared on unmount, so this scenario is unlikely in practice and matches existing patterns in the codebase.
  • No automated tests were added for the toggle behavior or Linux status detection. Consider adding unit or integration tests for the sandbox settings component.

Testing

  • Not run (automation)

Open Cowork Bot

seannair pushed a commit to seannair/open-cowork that referenced this pull request Jul 13, 2026
Four stacked issues were together causing bash tool calls to always fail
under Windows/WSL2 sandbox mode with "Working directory does not exist:
/workspace / Cannot execute bash commands." even when Settings correctly
reported WSL2 as ready.

1. WSLBridge.testDistro() made a single WSL echo probe with no retry.
   WSL2's lightweight VM can be cold/dormant on first use after boot or
   idle, causing a transient false negative that got cached as "WSL
   unavailable" for the rest of the app session. Now retries once after
   a short delay before giving up.

2. SandboxAdapter.initializeWSL() ran its own independent WSL status
   check instead of using the shared SandboxBootstrap check that drives
   the startup popup. Two concurrent checks at cold start could
   disagree, leaving the adapter stuck on a stale result even after the
   popup reported success. Now awaits the shared bootstrap instead of
   racing it.

3. The Settings sandbox.getStatus IPC handler never triggered adapter
   initialization, so checking Settings before starting any chat
   session always showed a hardcoded placeholder (Native/none)
   regardless of real WSL2 status. Now triggers on-demand
   initialization.

4. Root cause of the actual execution failure: @mariozechner/pi-coding-agent's
   createAgentSession() only reads tool names off the tools option to
   build an allow-list, then discards the actual Tool objects and
   rebuilds default (non-WSL) bash operations internally via
   createAllTools(cwd, ...). This silently dropped our WSL-routed bash
   operations (which cd into the sandbox path inside the WSL distro) on
   every single turn, regardless of session cache state, and fell back
   to the SDK's plain existsSync-based local bash operations, which
   then always failed because the sandbox path is a WSL-side path that
   does not exist on the Windows host. Fixed via a patch-package patch
   that additionally threads the real tool objects through as
   AgentSession's baseToolsOverride.

Verified via production logs: after this fix, the WslSandboxBash
executing-in-distro log line now actually fires, and bash commands
return real output from the synced sandbox filesystem instead of the
generic error.

Separate from OpenCoworkAI#293, which fixes the missing sandbox enable/disable
toggle and Linux status detection - that PR addresses whether sandbox
mode can be turned on at all; this one addresses whether commands
actually execute correctly once it is.
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.

1 participant