[P1] Workspace-path bind failure only warns; silently falls back to sandbox #41
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_shrimp#41
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
If a requested workdir fails to bind, the server falls back to a generated sandbox and the UI shows only a toast. Work can run somewhere other than where the operator intended without a hard signal.
Evidence
crates/hero_shrimp_web/ui/src/store.ts(workspace_path divergence → toast only).Proposed fix
On a real divergence, surface a persistent banner on the message (not a transient toast), and consider failing the dispatch when an explicit workdir can't be honored.
Filed from a comparative audit of Hero Shrimp vs Qwen-Code / kimi-cli / picoclaw (2026-05-23). Severity in title: P0=correctness/trust, P1=reliability/UX, P2=cleanup.
Implementation Spec for Issue #41
Objective
When a user's explicitly requested
workspaceDirdiverges from theworkspace_paththat the server actually ran the task in, replace the transient 4-second toast warning with a persistent error banner attached to the assistant message itself. The banner must remain visible until the user dismisses it or fixes the workspace. Additionally, add arequested_workspace_pathfield to themessage.sendRPC response so the UI can detect divergence from a clean server-supplied signal rather than a client-side heuristic.Requirements
result.workspace_pathdiverges fromworkspaceDir()(what the operator pinned), display a non-dismissing, persistently attached banner on the assistant message, not a transient toast.toast()call for workspace divergence must be removed.run_simple_chat_turn) must include arequested_workspace_pathfield in themessage.sendresponse so the client compares server-authoritative fields rather than re-running the normalization heuristic client-side.Msgtype must carry a new optionalworkspaceDivergencefield that records both the requested and actual paths, so the banner renders from stable message state.result.requested_workspace_pathvsresult.workspace_pathwhen both are present, falling back to the current client-side heuristic for older server builds.create_dir_allorcanonicalizeerrors on the requested path is correct — do NOT change this.ChatThread.tsxinside theMessagefunction, below theincompleteturn affordance and above thejobIdpanel.Files to Modify/Create
crates/hero_shrimp_server/src/rpc/methods/session.rsrequested_workspace_pathto therun_simple_chat_turnresponsecrates/hero_shrimp_web/ui/src/store.tsworkspaceDivergencetoMsgtype; replacetoast()with banner populationcrates/hero_shrimp_web/ui/src/components/WorkspaceDivergenceBanner.tsxcrates/hero_shrimp_web/ui/src/components/ChatThread.tsxMessagecomponentImplementation Plan
Step 1 — Server: Emit
requested_workspace_pathinmessage.sendresponseFiles:
crates/hero_shrimp_server/src/rpc/methods/session.rsIn the
Ok(raw_reply)branch ofrun_simple_chat_turn, in the block that buildscomplete_payloadandresponse(inside theif spec.include_workspace_in_response && let Some(path) = workspace_dirguards), after insertingworkspace_path, also insertrequested_workspace_pathwith the same value. This is a non-breaking additive change.Dependencies: none
Step 2 — Store: Add
workspaceDivergencetoMsgtypeFiles:
crates/hero_shrimp_web/ui/src/store.tsExtend the
Msgtype with:Dependencies: none (Step 3 fills this field)
Step 3 — Store: Replace
toast()with message-attached divergence insendMessageFiles:
crates/hero_shrimp_web/ui/src/store.tsIn
sendMessage, replace thetoast(...)call in the workspace divergence block with asetMessagescall that attachesworkspaceDivergence: { wanted, got }to the assistant message. Prefer comparingresult.requested_workspace_pathvsresult.workspace_path(server signal) and fall back to the existing client-side heuristic whenrequested_workspace_pathis absent.Dependencies: Step 2
Step 4 — New component:
WorkspaceDivergenceBanner.tsxFiles:
crates/hero_shrimp_web/ui/src/components/WorkspaceDivergenceBanner.tsx(new)Create a SolidJS functional component following the pattern of
StallBanner.tsx. Props:{ divergence: { wanted: string; got: string } }. Shows a red-bordered banner with requested path, actual path, explanation text, and a "Fix workspace" button that callssetWorkspacePickerOpen(true). Use Tailwind tokens already present in the codebase (border-bad/60,bg-bad/10,text-bad).Dependencies: none
Step 5 — ChatThread: Render the banner in
MessageFiles:
crates/hero_shrimp_web/ui/src/components/ChatThread.tsxInside the
Messagefunction, after the<Show when={props.msg.incomplete && !props.msg.streaming}>block and before the<Show when={props.msg.jobId}>block, add:Import
WorkspaceDivergenceBannerat the top.Dependencies: Step 4
Acceptance Criteria
workspaceDir()is set andresult.workspace_pathdiffers (after normalization), the assistant message displays a persistent red-bordered banner with the requested and actual paths.toast()for workspace divergence is removed — no transient warning is shown.workspaceDir()isnull, no banner appears.message.sendresponse includesrequested_workspace_pathequal toworkspace_pathwheninclude_workspace_in_responseis true.Msgtype change is backwards-compatible — existing messages render normally.create_dir_all/canonicalizefails) is unchanged.Notes
got.endsWith(w) || w.endsWith(got) || got.includes(w)) to avoid false positives from symlink resolution.workspace_pathfield is only included formode = "tools"turns (include_workspace_in_response: true). The banner correctly fires only on tools-mode turns.StallBanner.tsxexactly for styling tokens — do not introduce new CSS classes.requested_workspace_pathaddition is purely additive JSON.Test Results
cargo testwas run against the full workspace. Results:Failures (pre-existing, unrelated to this PR)
All 10 failures are pre-existing environment issues, not caused by the workspace divergence banner changes:
Environment constraints (9 failures):
tests::autonomy_auto_fallback_warns_when_no_isolated_backend_exists— bubblewrap is installed in this environment; test expected Host backendtests::autonomy_context_auto_selects_isolated_backends— same bubblewrap detection issuetools::external_cmd::tests::spawn_*(3 tests) —shnot available in test sandboxtools::tool_catalog::verify::e2e_datetime_server::phase*(2 tests) —python3not availableverification::runner::tests::command_*(2 tests) —shnot availablePre-existing code issue (1 failure):
skills::bundled_skills::tests::every_bundled_skill_file_is_parseable— 5 SKILL.md files have malformed YAML front-matter (indentation errors); pre-existing, not related to this PRThe files modified by this PR (
session.rs,store.ts,ChatThread.tsx,WorkspaceDivergenceBanner.tsx) have no dedicated unit tests — the fix is covered by the acceptance criteria verified manually via the UI.Implementation Summary
This PR addresses the P1 reliability/UX bug where workspace-path bind failure silently fell back to a sandbox with only a transient 4-second toast notification.
Changes Made
crates/hero_shrimp_server/src/rpc/methods/session.rsrequested_workspace_pathfield to themessage.sendresponse alongside the existingworkspace_pathfield, inside theinclude_workspace_in_responseguard.crates/hero_shrimp_web/ui/src/store.tsMsgtype with an optionalworkspaceDivergence?: { wanted: string; got: string }field to carry divergence state in message objects.sendMessage, replaced thetoast()call for workspace divergence with asetMessagescall that attachesworkspaceDivergenceto the assistant message. The comparison logic preferentially usesresult.requested_workspace_pathvsresult.workspace_path(server signal) and falls back to the existing client-side normalization heuristic whenrequested_workspace_pathis absent (backwards compatibility with older server builds).crates/hero_shrimp_web/ui/src/components/WorkspaceDivergenceBanner.tsx(new file)workspaceDivergence.setWorkspacePickerOpen(true).StallBanner.tsxusing existing Tailwind tokens (border-bad/60,bg-bad/10,text-bad).crates/hero_shrimp_web/ui/src/components/ChatThread.tsxWorkspaceDivergenceBanner.Messagefunction, added a<Show when={props.msg.workspaceDivergence}>block that renders the banner after the incomplete-turn affordance and before the job details panel.Behaviour Change
requested_workspace_pathpreferred when availablePull request opened: #70
This PR implements the changes discussed in this issue.