Skip to content

fix(think): bash tool deletes workspace directories under sandbox roots - #2239

Open
mattzcarey wants to merge 5 commits into
mainfrom
fix/think-bash-sandbox-root-sync
Open

mattzcarey wants to merge 5 commits into
mainfrom
fix/think-bash-sandbox-root-sync

Conversation

@mattzcarey

@mattzcarey mattzcarey commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Think's bash tool runs scripts against an in-memory snapshot of the Workspace and syncs changes back afterwards. The final sync pass deletes any directory that existed before the run and is missing from the shell's final tree. Three pieces interacted badly:

  • the snapshot walk records every workspace directory, including anything under /tmp, /bin, /usr, /dev, /proc, /sys;
  • shouldSyncBashPath filtered every path under those roots out of the final tree;
  • the deletion pass then saw those directories as "existed before, gone now" and ran rm(path, { recursive: true, force: true }).

So a Workspace holding /tmp/cache/data.txt lost /tmp/cache on every bash call, even one that never touched it. Files under those roots were synced back in an earlier pass and wiped by the directory pass moments later.

Fix: workspace-owned directories below a sandbox root are treated like any other directory (they were mounted from the snapshot, so their absence from the final tree does mean the script removed them), and only the roots themselves are skipped since the shell always materializes those.

Found while porting this sync engine into the pi harness in #2229, where Devin flagged the same bug in the copy.

Test plan

  • New test in assistant-tools.test.ts: seeded /tmp/cache/data.txt and /usr/notes.txt survive an unrelated script, and rm -rf /tmp/cache is still honoured. Verified red without the fix.
  • Full Think suite (pnpm vitest --run -c src/tests/vitest.config.ts in packages/think)
  • oxfmt, oxlint, repo-wide typecheck

Review fixes

Round 1

  • Ownership is now decided by ancestry, not exact path (shouldSyncBashPath). Devin's inline comment was right: with exact-path ownership, a file created or renamed inside a workspace-owned directory below a sandbox root was filtered out of the final tree while the deletion pass still removed the rename source, so mv /tmp/cache/data.txt /tmp/cache/renamed.txt destroyed the content and echo x > /tmp/cache/new.txt was silently dropped. A path now syncs if it is, or descends from, an initial workspace directory; the sandbox roots themselves are excluded as ancestors, so scratch written directly under /tmp or /usr still never persists.
  • Collapsed the duplicated /tmp special case into one BASH_SANDBOX_ROOTS = ["/tmp", ...BASH_EXCLUDED_SYNC_ROOTS] used by both isBashSandboxRoot and the sync predicate.
  • New test covering create, mkdir, and rename beneath /tmp/cache and beneath a workspace-owned /usr/project, asserting the renamed file keeps its content (Devin's repro) and that /tmp/loose.txt written directly under the root is still discarded.
  • Changeset reworded to describe the full data-loss fix (still patch).

Round 2

  • Synthetic builtins no longer leak through owned directories. just-bash writes a file per command into /bin and /usr/bin while constructing the shell (confirmed: a bare Bash boots with ~190 such paths). A workspace that happens to hold /usr/bin therefore claimed all of them under the new ancestry rule. shellInfrastructurePaths() derives the excluded subtrees by booting one throwaway Bash({ files: {}, cwd: "/" }) and reading fs.getAllPaths() — no hand-copied list, so the set tracks whatever the installed just-bash actually materializes — and memoizes it. Those paths are skipped as ancestry owners; an exact initial file such as /usr/bin/custom-tool still syncs, because shouldSyncBashPath matches it by path first.
  • Renames onto a sandbox root keep their content. mv /tmp/cache /tmp/archive left the destination with no initial ancestor, so it was dropped while the deletion pass removed the source. just-bash exposes no rename/mutation events and no journal on its in-memory fs, so provenance is inferred from the before/after trees by detectMovedWorkspaceEntries(): a new entry directly under a sandbox root is workspace content when an initial workspace path under that same root has vanished from the final tree and the new entry's contents match it exactly — same set of relative file paths, same bytes. Empty vanished subtrees are not matched (nothing to lose, and emptiness alone would adopt unrelated scratch). The rule is documented in the function's doc comment.
  • New tests: builtins under /usr/bin never appear in changedFiles or the workspace while a seeded /usr/bin/custom-tool survives; mv /tmp/cache /tmp/archive and mv /tmp/notes/note.txt /tmp/note.txt both keep their content, with the existing assertion that echo scratch > /tmp/loose.txt is still discarded.

Round 3

Devin's three follow-ups were all consequences of inferring moves from content equality, so the inference is gone: detectMovedWorkspaceEntries and its content-matching helpers are deleted and replaced with a real filesystem journal.

  • The sandbox now runs on a journalling filesystem. just-bash accepts a caller-supplied IFileSystem (BashOptions.fs) and exports InMemoryFs, so createJournalingBashFs() builds the snapshot's InMemoryFs and wraps mv, cp and rm to record every mutation. Verified against the installed just-bash: these methods always receive absolute paths already resolved against the script's cwd, including under cd /tmp && mv archive arch2.
  • Ownership propagates by provenance. resolveMovedWorkspaceEntries() replays the journal: a move whose source is workspace-owned at that point in the script makes its destination workspace-owned, and with it every path below. That applies transitively, so it survives edits made after the move, covers empty directories, chains through repeated moves and crosses sandbox roots. A move from unowned scratch, and a later rm of a destination, revoke it again.
  • Copy events are recorded but grant nothing. A directory mv is implemented inside just-bash as a cp of each entry plus an rm of the source (confirmed by journalling a real run), so a copy event cannot be distinguished from a move's own machinery. Events are recorded before the operation runs for the same reason — the outer mv must be replayed ahead of the calls it decomposes into.
  • Nothing else directly under a sandbox root is adopted, so byte-identical scratch after an unrelated rm stays scratch.
  • New tests, the three reported cases verbatim plus the two requested extras: mv then append; mv of an empty directory; rm followed by identical scratch that must not be adopted; a cross-root move (/tmp/cache/data.txt -> /usr/project/data.txt) together with a chained move (/tmp/chain -> /tmp/first -> /usr/second). The round-2 move tests and the loose-/tmp-scratch assertion are unchanged and still pass.

Devin Review

…andbox roots

The sync pass recorded every pre-existing directory, filtered anything under
/tmp, /bin, /usr, /dev, /proc and /sys out of the final tree, and then
treated those directories as deleted by the script and rm -rf'd them after
writing their files back. Workspace-owned directories below a sandbox root
now sync like any other directory; only the roots themselves are skipped.
@changeset-bot

changeset-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: dc3cd93

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@cloudflare/think Patch
@cloudflare/agent-think Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

devin-ai-integration[bot]

This comment was marked as resolved.

@agent-think

agent-think Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

⚪ agents import sizes

Measured 343 runtime imports as minified bundles. The primary size is gzip; raw minified size is included for diagnosis. An existing import growing by more than 10% is marked red. This report is informational.

Red Yellow Green Unchanged New Removed
0 0 0 343 0 0

Compared f6e556f3 with dc3cd932. Open workflow run.

No import sizes changed.

All 343 current runtime imports
Status Import Gzip Raw minified
agents#__DO_NOT_USE_WILL_BREAK__agentContext 261.1 KiB 1141.0 KiB
agents#__DO_NOT_USE_WILL_BREAK__withInvocationScope 261.1 KiB 1141.0 KiB
agents#Agent 261.1 KiB 1141.0 KiB
agents#AGENT_TOOL_MILESTONE_PART 261.1 KiB 1141.0 KiB
agents#AGENT_TOOL_PROGRESS_PART 261.1 KiB 1141.0 KiB
agents#buildAgentPath 261.7 KiB 1143.3 KiB
agents#buildAgentUrl 261.8 KiB 1143.7 KiB
agents#callable 261.1 KiB 1141.0 KiB
agents#camelCaseToKebabCase 261.1 KiB 1141.0 KiB
agents#createHeaderBasedEmailResolver 261.3 KiB 1141.4 KiB
agents#DEFAULT_AGENT_STATIC_OPTIONS 261.1 KiB 1141.0 KiB
agents#DurableObjectOAuthClientProvider 261.1 KiB 1141.0 KiB
agents#getAgentByName 261.1 KiB 1141.0 KiB
agents#getCurrentAgent 261.1 KiB 1141.0 KiB
agents#getSubAgentByName 261.4 KiB 1141.6 KiB
agents#isDurableObjectCodeUpdateReset 261.1 KiB 1141.0 KiB
agents#isDurableObjectMemoryLimitReset 261.1 KiB 1141.0 KiB
agents#isDurableObjectStorageReset 261.1 KiB 1141.0 KiB
agents#isPlatformTransientError 261.1 KiB 1141.0 KiB
agents#MCP_SERVER_ID_MAX_LENGTH 261.1 KiB 1141.0 KiB
agents#MessageType 261.2 KiB 1141.3 KiB
agents#normalizeServerId 261.1 KiB 1141.0 KiB
agents#parseSubAgentPath 261.1 KiB 1141.0 KiB
agents#routeAgentEmail 261.4 KiB 1141.7 KiB
agents#routeAgentRequest 261.7 KiB 1142.9 KiB
agents#routeSubAgentRequest 261.3 KiB 1141.5 KiB
agents#SqlError 261.1 KiB 1141.0 KiB
agents#StreamingResponse 261.1 KiB 1141.0 KiB
agents#SUB_PREFIX 261.1 KiB 1141.0 KiB
agents#unstable_callable 261.2 KiB 1141.2 KiB
agents/agent-tools#agentTool 112.5 KiB 538.2 KiB
agents/browser#BrowserConnector 50.5 KiB 176.6 KiB
agents/browser#browserContent 36.3 KiB 127.4 KiB
agents/browser#browserExtract 36.3 KiB 127.4 KiB
agents/browser#browserLinks 36.3 KiB 127.4 KiB
agents/browser#browserMarkdown 36.3 KiB 127.4 KiB
agents/browser#browserPdf 36.3 KiB 127.3 KiB
agents/browser#BrowserRenderingError 36.0 KiB 126.7 KiB
agents/browser#browserScrape 36.3 KiB 127.4 KiB
agents/browser#browserScreenshot 36.3 KiB 127.3 KiB
agents/browser#browserSnapshot 36.3 KiB 127.4 KiB
agents/browser#CdpSession 37.2 KiB 129.8 KiB
agents/browser#CodemodeRuntime 39.6 KiB 139.0 KiB
agents/browser#connectBrowser 37.8 KiB 131.4 KiB
agents/browser#connectBrowserSession 37.5 KiB 130.4 KiB
agents/browser#connectUrl 37.6 KiB 130.5 KiB
agents/browser#createBrowserSession 36.3 KiB 127.5 KiB
agents/browser#DEFAULT_EXEC_SWEEP_IDLE_MS 36.0 KiB 126.6 KiB
agents/browser#DEFAULT_SWEEP_IDLE_MS 36.0 KiB 126.6 KiB
agents/browser#deleteBrowserSession 36.1 KiB 126.9 KiB
agents/browser#DurableBrowserSessionStore 36.4 KiB 127.6 KiB
agents/browser#getBrowserRecording 36.2 KiB 127.1 KiB
agents/browser#listBrowserTargets 36.1 KiB 126.9 KiB
agents/browser#loadCdpSpec 36.6 KiB 128.3 KiB
agents/browser#runQuickAction 36.0 KiB 126.6 KiB
agents/browser/ai#createBrowserRuntime 147.0 KiB 632.8 KiB
agents/browser/ai#createBrowserTools 147.0 KiB 632.9 KiB
agents/browser/ai#createQuickActionTools 122.5 KiB 554.3 KiB
agents/browser/tanstack-ai#createBrowserTools 162.7 KiB 701.7 KiB
agents/channels#ChannelHost 3.3 KiB 9.1 KiB
agents/channels#consumeChunks 237 B 321 B
agents/channels#createUserIdentityStore 1.3 KiB 3.2 KiB
agents/channels#fallback 155 B 160 B
agents/channels#fallbackChannel 893 B 1.9 KiB
agents/channels#fanout 151 B 156 B
agents/channels#fanoutChannel 875 B 1.9 KiB
agents/channels#identityKey 178 B 223 B
agents/channels#isChannelMessageSurface 271 B 452 B
agents/channels#linkChannelIdentities 113 B 99 B
agents/channels#matchesPath 110 B 96 B
agents/channels#routes 307 B 507 B
agents/channels#UserIdentityConflictError 248 B 335 B
agents/channels/ai-sdk#createSendMessageTool 112.2 KiB 537.4 KiB
agents/channels/ai-sdk#toChannelChunks 112.5 KiB 538.4 KiB
agents/channels/email#email 23.5 KiB 75.4 KiB
agents/channels/email#inboundEmail 22.3 KiB 72.3 KiB
agents/channels/slack#slack 5.5 KiB 14.6 KiB
agents/channels/slack#slackWebhook 2.2 KiB 5.1 KiB
agents/channels/tanstack-ai#createSendMessageTool 16.2 KiB 68.4 KiB
agents/channels/telegram#telegram 3.8 KiB 10.3 KiB
agents/channels/telegram#telegramWebhook 1.6 KiB 3.6 KiB
agents/channels/voice#browserVoice 1010 B 2.2 KiB
agents/chat#AbortRegistry 2.5 KiB 8.9 KiB
agents/chat#AGENT_TOOL_STREAM_PROGRESS_BUMP_THROTTLE_MS 2.3 KiB 8.2 KiB
agents/chat#AgentToolProgressEmitter 2.7 KiB 9.5 KiB
agents/chat#AgentToolStreamProgressThrottle 2.4 KiB 8.3 KiB
agents/chat#aiSdkRecoveryCodec 2.3 KiB 8.2 KiB
agents/chat#applyAgentToolEvent 3.2 KiB 11.0 KiB
agents/chat#applyChunkToParts 2.3 KiB 8.2 KiB
agents/chat#applyToolUpdate 2.4 KiB 8.4 KiB
agents/chat#AutoContinuationController 2.3 KiB 8.2 KiB
agents/chat#awaitWithDeadline 2.4 KiB 8.4 KiB
agents/chat#broadcastTransition 3.2 KiB 11.4 KiB
agents/chat#buildChatRecoveringFrame 2.4 KiB 8.4 KiB
agents/chat#buildInClauseStrings 2.4 KiB 8.4 KiB
agents/chat#bumpChatRecoveryProgress 2.4 KiB 8.3 KiB
agents/chat#byteLength 2.5 KiB 8.5 KiB
agents/chat#CHAT_LAST_TERMINAL_KEY 2.3 KiB 8.2 KiB
agents/chat#CHAT_MESSAGE_TYPES 2.3 KiB 8.2 KiB
agents/chat#CHAT_RECOVERING_FLAG_TTL_MS 2.3 KiB 8.2 KiB
agents/chat#CHAT_RECOVERING_KEY 2.3 KiB 8.2 KiB
agents/chat#CHAT_RECOVERY_ALARM_DEBOUNCE_MS 2.3 KiB 8.2 KiB
agents/chat#CHAT_RECOVERY_INCIDENT_KEY_PREFIX 2.3 KiB 8.2 KiB
agents/chat#CHAT_RECOVERY_INCIDENT_TTL_MS 2.3 KiB 8.2 KiB
agents/chat#CHAT_RECOVERY_PROGRESS_KEY 2.3 KiB 8.2 KiB
agents/chat#CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS 2.3 KiB 8.2 KiB
agents/chat#CHAT_RECOVERY_TASK_NAME 2.3 KiB 8.2 KiB
agents/chat#CHAT_STREAM_PROGRESS_CREDIT_THROTTLE_MS 2.3 KiB 8.2 KiB
agents/chat#ChatRecoveryEngine 4.4 KiB 15.3 KiB
agents/chat#chatRecoveryTaskRunOptions 2.4 KiB 8.6 KiB
agents/chat#ChatStreamStalledError 2.4 KiB 8.3 KiB
agents/chat#classifyAgentToolChildRecovery 2.4 KiB 8.5 KiB
agents/chat#clearChatTerminal 2.3 KiB 8.3 KiB
agents/chat#clientResolvableToolNames 2.3 KiB 8.3 KiB
agents/chat#ContinuationState 2.7 KiB 9.8 KiB
agents/chat#createAgentToolEventState 2.3 KiB 8.3 KiB
agents/chat#createChatFiberSnapshot 2.5 KiB 8.6 KiB
agents/chat#createChatRecoveryTaskDefinition 2.6 KiB 9.0 KiB
agents/chat#createChatStreams 6.4 KiB 22.1 KiB
agents/chat#createChatTurnTaskDefinition 2.7 KiB 8.9 KiB
agents/chat#createToolsFromClientSchemas 114.3 KiB 545.4 KiB
agents/chat#crossMessageToolResultUpdate 2.4 KiB 8.6 KiB
agents/chat#DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS 2.3 KiB 8.2 KiB
agents/chat#DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES 2.3 KiB 8.2 KiB
agents/chat#DEFAULT_CHAT_RECOVERY_MAX_WORK 2.3 KiB 8.2 KiB
agents/chat#DEFAULT_CHAT_RECOVERY_NO_PROGRESS_TIMEOUT_MS 2.3 KiB 8.2 KiB
agents/chat#DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS 2.3 KiB 8.2 KiB
agents/chat#DEFAULT_CHAT_RECOVERY_TERMINAL_MESSAGE 2.4 KiB 8.3 KiB
agents/chat#dispatchChatRecoveryToHandoff 3.0 KiB 9.9 KiB
agents/chat#drainInteractionApplies 2.3 KiB 8.3 KiB
agents/chat#enforceRowSizeLimit 3.5 KiB 11.2 KiB
agents/chat#hasIncompleteToolBatch 2.4 KiB 8.6 KiB
agents/chat#interceptAgentToolBroadcast 2.5 KiB 8.7 KiB
agents/chat#isPlatformFailure 2.6 KiB 8.9 KiB
agents/chat#isReplayChunk 2.4 KiB 8.7 KiB
agents/chat#iterateWithStallWatchdog 2.6 KiB 8.8 KiB
agents/chat#KV_DELETE_MAX_KEYS 2.3 KiB 8.2 KiB
agents/chat#listActiveChatRecoveryIncidents 2.4 KiB 8.4 KiB
agents/chat#MAX_BOUND_PARAMS 2.3 KiB 8.2 KiB
agents/chat#MessageType 2.4 KiB 9.0 KiB
agents/chat#normalizeToolInput 2.3 KiB 8.2 KiB
agents/chat#parseProtocolMessage 2.5 KiB 9.0 KiB
agents/chat#partAwaitsClientInteraction 2.4 KiB 8.6 KiB
agents/chat#pausedExecutionUpdate 2.4 KiB 8.4 KiB
agents/chat#pendingChatTerminal 2.3 KiB 8.3 KiB
agents/chat#persistReconstructedOrphan 3.1 KiB 11.0 KiB
agents/chat#PreStreamTurns 2.6 KiB 9.3 KiB
agents/chat#readChatRecoveryProgress 2.3 KiB 8.3 KiB
agents/chat#reconcileMessages 2.8 KiB 9.6 KiB
agents/chat#reconcileOrphanPartial 2.4 KiB 8.5 KiB
agents/chat#recordChatTerminal 2.4 KiB 8.3 KiB
agents/chat#repairInterruptedToolParts 2.6 KiB 9.1 KiB
agents/chat#resolveChatRecoveryConfig 2.6 KiB 8.9 KiB
agents/chat#resolveToolMergeId 2.4 KiB 8.5 KiB
agents/chat#ResumableStream 5.1 KiB 16.8 KiB
agents/chat#ResumeHandshake 3.0 KiB 10.4 KiB
agents/chat#ROW_MAX_BYTES 2.3 KiB 8.2 KiB
agents/chat#runChatRecoveryExhaustion 2.6 KiB 8.9 KiB
agents/chat#sanitizeMessage 2.5 KiB 8.8 KiB
agents/chat#sendIfOpen 2.4 KiB 8.4 KiB
agents/chat#setChatRecovering 2.5 KiB 8.5 KiB
agents/chat#shouldCreditStreamProgress 2.4 KiB 8.3 KiB
agents/chat#STREAM_RESUME_NONE_REASONS 2.3 KiB 8.2 KiB
agents/chat#StreamAccumulator 2.9 KiB 10.7 KiB
agents/chat#StreamProgressCreditThrottle 2.4 KiB 8.3 KiB
agents/chat#SubmitConcurrencyController 2.9 KiB 10.3 KiB
agents/chat#sweepStaleChatRecoveryIncidents 2.4 KiB 8.5 KiB
agents/chat#TextSegmentJoiner 2.7 KiB 9.2 KiB
agents/chat#TIMED_OUT 2.3 KiB 8.2 KiB
agents/chat#toolApprovalUpdate 2.4 KiB 8.5 KiB
agents/chat#toolPartHasSettledResult 2.3 KiB 8.4 KiB
agents/chat#toolResultUpdate 2.4 KiB 8.5 KiB
agents/chat#truncateOlderMessages 3.2 KiB 10.4 KiB
agents/chat#TurnQueue 2.6 KiB 9.2 KiB
agents/chat#unwrapChatFiberSnapshot 2.4 KiB 8.5 KiB
agents/chat#wrapChatFiberSnapshot 2.3 KiB 8.2 KiB
agents/chat-sdk#ChatSdkStateAdapter 263.5 KiB 1152.6 KiB
agents/chat-sdk#ChatSdkStateAgent 262.8 KiB 1150.0 KiB
agents/chat-sdk#createChatSdkState 263.5 KiB 1152.6 KiB
agents/chat-sdk#defaultKeyShard 261.2 KiB 1141.2 KiB
agents/chat-sdk#defaultThreadShard 261.1 KiB 1141.0 KiB
agents/chat/react#detectToolsRequiringConfirmation 3.3 KiB 8.3 KiB
agents/chat/react#extractClientToolSchemas 3.2 KiB 8.3 KiB
agents/chat/react#getAgentMessages 3.4 KiB 8.6 KiB
agents/chat/react#getToolApproval 3.1 KiB 8.0 KiB
agents/chat/react#getToolCallId 3.1 KiB 8.0 KiB
agents/chat/react#getToolInput 3.1 KiB 8.0 KiB
agents/chat/react#getToolOutput 3.1 KiB 8.0 KiB
agents/chat/react#getToolPartState 3.2 KiB 8.2 KiB
agents/chat/react#useAgentChat 132.9 KiB 609.7 KiB
agents/chat/react#WebSocketChatTransport 5.7 KiB 17.1 KiB
agents/chat/transport#WebSocketChatTransport 2.8 KiB 9.2 KiB
agents/client#AgentClient 21.8 KiB 75.2 KiB
agents/client#AgentConnectionError 16.3 KiB 57.8 KiB
agents/client#agentFetch 19.7 KiB 69.0 KiB
agents/client#createStubProxy 16.4 KiB 57.8 KiB
agents/client#DEFAULT_CALL_TIMEOUT_MS 16.2 KiB 57.6 KiB
agents/client#isTerminalCloseEvent 16.3 KiB 57.6 KiB
agents/client#nativeCall 16.5 KiB 58.3 KiB
agents/client#NativeCallQueue 16.7 KiB 58.9 KiB
agents/client#splitCallOptions 16.3 KiB 57.7 KiB
agents/context#AgentContextProvider 412 B 792 B
agents/context#AgentSearchProvider 640 B 1.3 KiB
agents/context#ContextBlocks 87.4 KiB 429.7 KiB
agents/email#createAddressBasedEmailResolver 193 B 227 B
agents/email#createCatchAllEmailResolver 110 B 97 B
agents/email#createHeaderBasedEmailResolver 334 B 492 B
agents/email#createSecureReplyEmailResolver 718 B 1.3 KiB
agents/email#DEFAULT_MAX_AGE_SECONDS 56 B 39 B
agents/email#isAutoReplyEmail 201 B 249 B
agents/email#signAgentHeaders 424 B 812 B
agents/experimental/webmcp#registerWebMcp 85.2 KiB 295.8 KiB
agents/lifecycle#getCurrentAgent 376 B 798 B
agents/lifecycle#Lifecycle 8.4 KiB 26.1 KiB
agents/lifecycle#LifecycleCapability 497 B 999 B
agents/mcp#createLegacyMcpHandler 378.6 KiB 1583.3 KiB
agents/mcp#createMcpHandler 390.9 KiB 1628.5 KiB
agents/mcp#DurableObjectEventStore 345.1 KiB 1441.8 KiB
agents/mcp#ElicitRequestSchema 345.1 KiB 1441.8 KiB
agents/mcp#experimental_createMcpHandler 378.7 KiB 1583.6 KiB
agents/mcp#getMcpAuthContext 345.1 KiB 1441.9 KiB
agents/mcp#MCP_SERVER_ID_MAX_LENGTH 345.1 KiB 1441.9 KiB
agents/mcp#McpAgent 345.1 KiB 1441.8 KiB
agents/mcp#normalizeServerId 345.1 KiB 1441.8 KiB
agents/mcp#RPC_DO_PREFIX 345.1 KiB 1441.8 KiB
agents/mcp#RPCClientTransport 345.1 KiB 1441.8 KiB
agents/mcp#RPCServerTransport 345.1 KiB 1441.8 KiB
agents/mcp#SSEEdgeClientTransport 345.2 KiB 1442.1 KiB
agents/mcp#StreamableHTTPEdgeClientTransport 345.2 KiB 1442.1 KiB
agents/mcp#WorkerTransport 348.5 KiB 1458.7 KiB
agents/mcp/client#getNamespacedData 62.9 KiB 240.0 KiB
agents/mcp/client#MCP_SERVER_ID_MAX_LENGTH 62.9 KiB 239.9 KiB
agents/mcp/client#MCPClientManager 158.6 KiB 702.6 KiB
agents/mcp/client#normalizeServerId 63.0 KiB 240.2 KiB
agents/mcp/do-oauth-client-provider#DurableObjectOAuthClientProvider 2.1 KiB 6.6 KiB
agents/mcp/server#createMcpHandler 80.5 KiB 307.2 KiB
agents/mcp/server#getMcpAuthContext 64.0 KiB 245.5 KiB
agents/observability#channels 259 B 549 B
agents/observability#genericObservability 470 B 1.2 KiB
agents/observability#subscribe 324 B 668 B
agents/observability/ai#wrapAISDK 8.8 KiB 30.5 KiB
agents/queue#Queue 3.1 KiB 9.6 KiB
agents/react#_testUtils 19.3 KiB 66.4 KiB
agents/react#useAgent 27.1 KiB 89.9 KiB
agents/react#useAgentToolEvents 21.1 KiB 73.7 KiB
agents/routing#getAgentByName 795 B 1.7 KiB
agents/routing#routeAgentRequest 1.6 KiB 3.6 KiB
agents/routing#RoutedAgents 2.4 KiB 6.2 KiB
agents/schedule#getSchedulePrompt 85.8 KiB 424.7 KiB
agents/schedule#scheduleSchema 85.3 KiB 423.6 KiB
agents/schedule#unstable_getSchedulePrompt 85.9 KiB 424.9 KiB
agents/schedule#unstable_scheduleSchema 85.3 KiB 423.6 KiB
agents/schedules#Scheduler 6.8 KiB 22.0 KiB
agents/schedules/parser#getSchedulePrompt 85.8 KiB 424.7 KiB
agents/schedules/parser#scheduleSchema 85.3 KiB 423.6 KiB
agents/sessions#COMPACTION_PREFIX 147 B 160 B
agents/sessions#createCompactFunction 1.7 KiB 4.0 KiB
agents/sessions#isCompactionMessage 177 B 200 B
agents/sessions#Session 2.0 KiB 6.3 KiB
agents/sessions#Sessions 9.6 KiB 34.3 KiB
agents/skills#fromManifest 309.8 KiB 1084.0 KiB
agents/skills#parseSkillFrontmatter 328.4 KiB 1146.2 KiB
agents/skills#parseSkillMarkdown 328.6 KiB 1146.5 KiB
agents/skills#r2 330.2 KiB 1150.4 KiB
agents/skills#runner 369.0 KiB 1297.8 KiB
agents/skills#SkillRegistry 416.4 KiB 1581.7 KiB
agents/skills/compile#compileSkillScript 15.4 KiB 43.4 KiB
agents/skills/compile#isCompilableSkillScript 15.4 KiB 43.3 KiB
agents/state#State 1.1 KiB 2.4 KiB
agents/streams#DEFAULT_MAX_CHUNK_BYTES 94 B 96 B
agents/streams#sseResponse 857 B 1.6 KiB
agents/streams#StreamClosedError 175 B 212 B
agents/streams#StreamNotFoundError 215 B 276 B
agents/streams#Streams 4.3 KiB 13.9 KiB
agents/streams#StreamSerializationError 171 B 201 B
agents/tasks#DuplicateTaskStepError 328 B 463 B
agents/tasks#MAX_SERIALIZED_BYTES 190 B 232 B
agents/tasks#MissingTaskDefinitionError 358 B 536 B
agents/tasks#NonRetryableError 238 B 308 B
agents/tasks#TaskReplayDivergedError 341 B 483 B
agents/tasks#Tasks 8.9 KiB 31.8 KiB
agents/tasks#TaskSerializationError 258 B 339 B
agents/types#MessageType 211 B 365 B
agents/vite#default 353.8 KiB 1356.1 KiB
agents/voice#addSFUTracks 324 B 424 B
agents/voice#createSFUSession 255 B 306 B
agents/voice#createSFUWebSocketAdapter 331 B 429 B
agents/voice#decodeVarint 157 B 160 B
agents/voice#downsample48kStereoTo16kMono 238 B 324 B
agents/voice#encodePayloadToProtobuf 189 B 279 B
agents/voice#encodeVarint 129 B 122 B
agents/voice#extractPayloadFromProtobuf 271 B 425 B
agents/voice#iterateText 1.6 KiB 3.8 KiB
agents/voice#renegotiateSFUSession 329 B 428 B
agents/voice#SentenceChunker 550 B 1.1 KiB
agents/voice#sfuFetch 294 B 358 B
agents/voice#upsample16kMonoTo48kStereo 211 B 272 B
agents/voice#VOICE_PROTOCOL_VERSION 51 B 31 B
agents/voice#withVoice 9.5 KiB 32.7 KiB
agents/voice#withVoiceInput 4.8 KiB 16.0 KiB
agents/voice#WorkersAIFluxSTT 1.5 KiB 4.3 KiB
agents/voice#WorkersAINova3STT 1.6 KiB 4.3 KiB
agents/voice#WorkersAITTS 682 B 1.4 KiB
agents/voice/client#VOICE_PROTOCOL_VERSION 473 B 768 B
agents/voice/client#VoiceClient 10.1 KiB 31.6 KiB
agents/voice/client#WebSocketVoiceTransport 4.6 KiB 13.3 KiB
agents/voice/errors#logVoiceError 132 B 173 B
agents/voice/errors#toVoiceError 94 B 80 B
agents/voice/errors#voiceErrorMessage 125 B 111 B
agents/voice/errors#VoiceProviderError 190 B 345 B
agents/voice/react#useVoiceAgent 13.6 KiB 42.5 KiB
agents/voice/react#useVoiceInput 13.3 KiB 41.4 KiB
agents/voice/react#WebSocketVoiceTransport 7.4 KiB 21.3 KiB
agents/voice/sfu#addSFUTracks 323 B 424 B
agents/voice/sfu#createSFUSession 254 B 306 B
agents/voice/sfu#createSFUWebSocketAdapter 329 B 429 B
agents/voice/sfu#decodeVarint 155 B 160 B
agents/voice/sfu#downsample48kStereoTo16kMono 236 B 324 B
agents/voice/sfu#encodePayloadToProtobuf 188 B 279 B
agents/voice/sfu#encodeVarint 129 B 122 B
agents/voice/sfu#extractPayloadFromProtobuf 270 B 425 B
agents/voice/sfu#renegotiateSFUSession 329 B 428 B
agents/voice/sfu#sfuFetch 292 B 358 B
agents/voice/sfu#upsample16kMonoTo48kStereo 209 B 272 B
agents/voice/text#iterateText 1.6 KiB 3.8 KiB
agents/voice/text#SentenceChunker 549 B 1.1 KiB
agents/voice/types#VOICE_PROTOCOL_VERSION 51 B 31 B
agents/voice/workers-ai#WorkersAIFluxSTT 1.5 KiB 4.3 KiB
agents/voice/workers-ai#WorkersAINova3STT 1.6 KiB 4.3 KiB
agents/voice/workers-ai#WorkersAITTS 682 B 1.4 KiB
agents/websockets#CAPNWEB_TRANSPORT_QUERY 12.4 KiB 43.3 KiB
agents/websockets#CAPNWEB_TRANSPORT_VALUE 12.4 KiB 43.3 KiB
agents/websockets#capnWebTransportUrl 12.5 KiB 43.5 KiB
agents/websockets#CF_NO_PROTOCOL_KEY 12.4 KiB 43.3 KiB
agents/websockets#CF_READONLY_KEY 12.4 KiB 43.3 KiB
agents/websockets#isCapnWebTransportUpgrade 12.5 KiB 43.4 KiB
agents/websockets#registerInternalConnectionKeys 12.4 KiB 43.4 KiB
agents/websockets#WebSockets 20.0 KiB 69.3 KiB
agents/workflows#AgentWorkflow 262.4 KiB 1145.7 KiB
agents/workflows#WorkflowRejectedError 261.2 KiB 1141.2 KiB
agents/x402#normalizeNetwork 14.7 KiB 61.1 KiB
agents/x402#withX402 23.0 KiB 89.2 KiB
agents/x402#withX402Client 104.1 KiB 346.5 KiB

Reported by agent-think[bot].

@pkg-pr-new

pkg-pr-new Bot commented Sep 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@2239

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@2239

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@2239

hono-agents

npm i https://pkg.pr.new/hono-agents@2239

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@2239

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@2239

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@2239

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@2239

commit: dc3cd93

Workspace ownership in the bash sync was exact-path: only the files and
directories the snapshot recorded synced back. A file created, or a file
renamed, inside a workspace-owned directory below a sandbox root was
dropped by shouldSyncBashPath while the deletion pass still removed the
rename source, so `mv /tmp/cache/data.txt /tmp/cache/renamed.txt` lost
the content outright.

Decide ownership by ancestry instead, matching the doc comment: a path
syncs if it is, or descends from, an initial workspace directory, with
the sandbox roots themselves excluded as ancestors so scratch written
directly under /tmp or /usr still never persists. Collapse the duplicated
/tmp special case into a single BASH_SANDBOX_ROOTS constant.
devin-ai-integration[bot]

This comment was marked as resolved.

…dbox-root renames

Derive the shell's own infrastructure subtrees by booting a throwaway
just-bash and reading its filesystem, then exclude them from
ancestry-based ownership: a workspace holding /usr/bin no longer adopts
the file-per-builtin the shell writes there, while exact workspace files
under those paths keep syncing.

Renames that land directly on a sandbox root (mv /tmp/cache /tmp/archive,
or a file moved out of an owned subdirectory) had no initial ancestor, so
the destination was dropped while the deletion pass removed the source.
just-bash exposes no rename events or fs journal, so provenance is
inferred from the before/after trees: a new entry directly under a
sandbox root is owned when an initial workspace path under the same root
vanished and the new entry's contents match it exactly.
devin-ai-integration[bot]

This comment was marked as resolved.

…tent matching

Inferring moves from content equality was wrong in three ways: a file
edited after its move no longer matched, an empty directory had nothing
to match on, and scratch that happened to equal a deleted workspace file
was adopted as its destination.

just-bash accepts a caller-supplied IFileSystem, so the sandbox is now
built on an InMemoryFs whose mv/cp/rm are wrapped to record a journal.
Replaying it gives real provenance: a move from a workspace-owned source
makes its destination workspace-owned, transitively and regardless of
what happens to the content afterwards, while a later rm of the
destination revokes it. Copy events are recorded but grant nothing,
because a directory mv is implemented internally as cp plus rm.

Nothing else created directly under a sandbox root is adopted, so
identical scratch stays scratch.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 1 new potential issue.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment on lines +1645 to +1651
fs.mv = (source, destination) => {
journal.push({
type: "move",
source: normalizeWorkspacePath(source),
destination: normalizeWorkspacePath(destination)
});
return mv(source, destination);

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.

🟡 Failed moves persist sandbox scratch

When mv fails for an owned source, journal still marks its destination as owned. Existing scratch there then persists into the workspace.

Learn more

Move ownership is based on journal entries, not the final filesystem alone. The wrapper appends a move before the underlying operation settles, so rejected moves remain indistinguishable from successful moves during resolveMovedWorkspaceEntries. Recording only after success would lose the required ordering relative to the nested cp and rm events, so the journal needs an explicit operation result or transaction boundary.

Example: Start with workspace file /tmp/cache/data.txt. Run echo scratch > /tmp/archive; mv /tmp/cache /tmp/archive. InMemoryFs.mv rejects because the destination is a file, but /tmp/archive remains in the final sandbox and the journal grants it ownership. The sync writes scratch into the workspace even though direct scratch under /tmp is excluded.

Recommended fix: Record move lifecycle events with an operation identifier. Replay a move only when its matching completion event confirms success, while preserving the outer move's position before nested cp and rm events. Apply the same success semantics to removal events that can reject.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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