Skip to content

fix(chat): scope troubleshoot handoff to its workspace#5344

Merged
waleedlatif1 merged 2 commits into
stagingfrom
fix/mothership-handoff-workspace-scope
Jul 2, 2026
Merged

fix(chat): scope troubleshoot handoff to its workspace#5344
waleedlatif1 merged 2 commits into
stagingfrom
fix/mothership-handoff-workspace-scope

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Follow-up to feat(logs): add Troubleshoot in Chat button for errored runs #5341 (merged), addressing a Cursor Bugbot Medium that landed after that PR was merged, so it shipped to staging.
  • MothershipHandoffStorage (the one-shot "Troubleshoot in Chat" handoff) used a single global localStorage key with no workspace scoping. If a workspace-A handoff was consumed by a different workspace's /home within the TTL (e.g. a navigation race, or a /chat/[id] mount skipping consume then landing on another workspace's new chat), the prompt auto-sent with workspace A's executionId — which doesn't resolve in workspace B, so the logs context is dropped server-side and Sim can't see the failure.

Fix

  • store(handoff, workspaceId) now records the target workspaceId.
  • consume(workspaceId) only consumes (and clears) a handoff whose workspaceId matches; a handoff owned by a different workspace is left untouched for its owner rather than misfired. Legacy/corrupt entries (no workspaceId) are still tombstoned so nothing lingers.
  • Callers updated: the log-details Troubleshoot action passes its workspaceId to store; the home surface passes its workspaceId to consume.

Type of Change

  • Bug fix

Testing

  • Unit tests extended: cross-workspace handoff is left for its owner (not consumed, not cleared) and the owning workspace still consumes it; store rejects an empty workspace; corrupt/expired entries still tombstone. type-check and biome pass.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

MothershipHandoffStorage now records the target workspaceId and only the
matching workspace consumes (and clears) it; a different workspace leaves it
untouched for its owner. Prevents a workspace-A handoff from firing in
workspace B, where A's executionId can't resolve and the run context is
dropped.
@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 2, 2026 12:33am

Request Review

@cursor

cursor Bot commented Jul 2, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Small, localized client-side storage and call-site change with expanded unit tests; no auth or server behavior changes.

Overview
Troubleshoot in Chat one-shot localStorage handoffs are now tied to a workspace ID, so another workspace’s /home mount cannot auto-send a prompt with the wrong run’s executionId (where logs context would fail to resolve).

MothershipHandoffStorage.store persists workspaceId and rejects empty workspace; consume(workspaceId) only clears and returns a matching handoff—foreign workspaces leave the entry intact for the owner. Pre-scoping legacy entries (message + timestamp, no workspaceId) are tombstoned on consume instead of firing in the current workspace. Home and log-details pass their route workspaceId; unit tests cover cross-workspace isolation, empty workspace, legacy discard, and existing expiry/corruption behavior.

Reviewed by Cursor Bugbot for commit 59e881e. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR scopes MothershipHandoffStorage ("Troubleshoot in Chat") to its originating workspace, preventing a cross-workspace misfire where a navigation race could cause workspace B to consume workspace A's handoff and auto-send a message with an unresolvable executionId.

  • browser-storage.ts: store() now records workspaceId in the stored payload and rejects an empty workspace; consume() takes workspaceId as its first parameter, skips (preserves) entries owned by a different workspace, and tombstones legacy/corrupt entries.
  • Callers updated: log-details.tsx passes its workspaceId to store(); home.tsx passes its workspaceId to consume() and adds workspaceId to the effect's dependency array.
  • Tests extended: new cases cover cross-workspace isolation, empty-workspace rejection, and legacy-format tombstoning.

Confidence Score: 5/5

The change is safe to merge — it tightens an existing one-shot localStorage mechanism to prevent cross-workspace misfires without touching any data-persistence, API, or rendering paths.

All callers (store and consume) are updated, the guard logic correctly preserves a mismatched entry for its owner while tombstoning corrupt/legacy entries, the 60-second TTL is unchanged, and the new test suite locks in every relevant path including the legacy-format and cross-workspace isolation scenarios.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/lib/core/utils/browser-storage.ts Core fix: store() now records workspaceId and rejects empty values; consume() guards against cross-workspace reads while tombstoning legacy/corrupt entries. Logic is correct and well-documented.
apps/sim/lib/core/utils/browser-storage.test.ts Tests updated to cover workspace scoping, cross-workspace isolation, empty-workspace rejection, and legacy-format tombstoning. All key paths are exercised.
apps/sim/app/workspace/[workspaceId]/home/home.tsx Passes workspaceId (from useParams) to consume() and correctly adds it to the effect's dependency array.
apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx Passes workspaceId to store() so the stored handoff is correctly scoped to the originating workspace.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["User clicks 'Troubleshoot in Chat'\n(log-details.tsx)"] --> B["sendMothershipMessage(message, context)"]
    B -->|"Mothership already\nmounted (success)"| C["Message sent directly — done"]
    B -->|"Mothership not mounted\n(returns false)"| D["MothershipHandoffStorage.store(\n{ message, contexts }, workspaceId\n)"]
    D -->|"empty message or\nempty workspaceId"| E["return false — no navigation"]
    D -->|"valid → writes\n{ message, contexts, workspaceId, timestamp }\nto localStorage"| F["router.push workspace/home"]
    F --> G["Home mounts\n(home.tsx — chatId is null)"]
    G --> H["MothershipHandoffStorage.consume(workspaceId)"]
    H --> I{{"Entry in\nlocalStorage?"}}
    I -->|"No"| J["return null — nothing fires"]
    I -->|"Yes"| K{{"data.workspaceId\nmatches?"}}
    K -->|"Different workspace\n(mismatch)"| L["return null\nentry PRESERVED\nfor its owner"]
    K -->|"Matches OR\nlegacy (no workspaceId)"| M["clear() — tombstone"]
    M --> N{{"Valid?\n(workspaceId present,\nmessage, timestamp,\nwithin 60s TTL)"}}
    N -->|"No — legacy / corrupt / expired"| O["return null"]
    N -->|"Yes"| P["return { message, contexts }"]
    P --> Q["sendMessage auto-fires\nwith correct workspace context"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["User clicks 'Troubleshoot in Chat'\n(log-details.tsx)"] --> B["sendMothershipMessage(message, context)"]
    B -->|"Mothership already\nmounted (success)"| C["Message sent directly — done"]
    B -->|"Mothership not mounted\n(returns false)"| D["MothershipHandoffStorage.store(\n{ message, contexts }, workspaceId\n)"]
    D -->|"empty message or\nempty workspaceId"| E["return false — no navigation"]
    D -->|"valid → writes\n{ message, contexts, workspaceId, timestamp }\nto localStorage"| F["router.push workspace/home"]
    F --> G["Home mounts\n(home.tsx — chatId is null)"]
    G --> H["MothershipHandoffStorage.consume(workspaceId)"]
    H --> I{{"Entry in\nlocalStorage?"}}
    I -->|"No"| J["return null — nothing fires"]
    I -->|"Yes"| K{{"data.workspaceId\nmatches?"}}
    K -->|"Different workspace\n(mismatch)"| L["return null\nentry PRESERVED\nfor its owner"]
    K -->|"Matches OR\nlegacy (no workspaceId)"| M["clear() — tombstone"]
    M --> N{{"Valid?\n(workspaceId present,\nmessage, timestamp,\nwithin 60s TTL)"}}
    N -->|"No — legacy / corrupt / expired"| O["return null"]
    N -->|"Yes"| P["return { message, contexts }"]
    P --> Q["sendMessage auto-fires\nwith correct workspace context"]
Loading

Reviews (2): Last reviewed commit: "test(chat): cover legacy no-workspaceId ..." | Re-trigger Greptile

Comment thread apps/sim/lib/core/utils/browser-storage.test.ts
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 59e881e. Configure here.

@waleedlatif1 waleedlatif1 merged commit 577b402 into staging Jul 2, 2026
17 checks passed
@waleedlatif1 waleedlatif1 deleted the fix/mothership-handoff-workspace-scope branch July 2, 2026 00:57
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