Skip to content

fix(flows): persist + rehydrate the Copilot chat across reload (B11)#4663

Merged
graycyrus merged 5 commits into
tinyhumansai:mainfrom
graycyrus:feat/flows-copilot-chat-persistence
Jul 7, 2026
Merged

fix(flows): persist + rehydrate the Copilot chat across reload (B11)#4663
graycyrus merged 5 commits into
tinyhumansai:mainfrom
graycyrus:feat/flows-copilot-chat-persistence

Conversation

@graycyrus

@graycyrus graycyrus commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Stacks on PR-B4 (#4662) — merge that first; this diff shows B4's changes until then.

The bug

The Flows Copilot chat is not persistent: reopening the panel, switching flows and back, or reloading the app always started the conversation empty. Two gaps:

  • workflowCopilotThreads.ts cached each flow's copilot thread id in a module-level Map — lost on any reload.
  • Even when the thread id survived (same session), useWorkflowBuilderChat never re-fetched that thread's messages/turn state from the core on mount. messagesByThreadId isn't in the redux-persist whitelist, so it starts empty on a fresh app load. The messages themselves are durable server-side (threadApi.appendMessage persists every turn) — they just never got loaded back.

The fix

  • workflowCopilotThreads.ts: back the flow → thread-id cache with localStorage instead of an in-memory Map, namespaced ${userId}:copilot-thread:<flow> (the same ${userId}: convention userScopedStorage/clearAllAppData use elsewhere, [Bug] Auth state leaks across users on same device — user B sees user A's threads, accounts, notifications #900/Fix account switching causing data loss and forced re-onboarding after re-login #983) so an identity flip or a per-user "clear my data" can't leak/destroy another user's mapping. Wrapped in try/catch so private-mode/quota errors degrade to a no-op (copilot just starts a fresh thread) instead of throwing.
  • useWorkflowBuilderChat: on mount, when seedThreadId names a thread this hook did not just create itself via send() (tracked via a createdThreadIdRef guard), dispatch loadThreadMessages + fetchAndHydrateTurnState + fetchAndHydrateTurnHistory to rehydrate the full transcript + turn state from the core (mirrors Conversations.tsx's thread-switch effect).
  • The createdThreadIdRef guard matters because WorkflowCopilotPanel reports every threadId change back up via onThreadIdChange, and a caller re-passes that straight back in as seedThreadId on the next render — without the guard, a freshly-created thread's first render would look identical to "an existing persisted thread was just selected" and trigger a rehydrate that races the in-flight turn (loadThreadMessages.fulfilled replaces the thread's message array wholesale, so a fetch resolving before the backend catches up would wipe the just-appended turn back out).

Why this depends on B4

Rehydration reloads the thread's full transcript, including the between-tool narration bubbles tagged isInterim in B4. Without B4's displayMessages filter in WorkflowCopilotPanel, those interim messages would render as bubbles again every time the panel reopens/reloads — this PR's rehydration effect is only correct on top of B4.

Verification

  • pnpm typecheck, eslint, format:check — clean on all changed files.
  • vitest run on the touched test files — 35/35 passing (localStorage round-trip + user-scoping + reload simulation in workflowCopilotThreads.test.ts; rehydration-dispatch + createdThreadIdRef race-guard tests in useWorkflowBuilderChat.test.ts).
  • Full vitest run suite: 8399 passed / 2 failed / 2 skipped. Both failures are pre-existing and unrelated to this diff: rpcMethods.test.ts's drift guard hits ENOENT because the vendor/tinychannels submodule isn't initialized in this worktree, and IntelligenceOrchestrationTab.test.tsx passes cleanly in isolation (flaky under full-suite parallelism, unrelated to Flows/Copilot).

Note

Split from #4650 (previously combined with B4 in one PR since they touched the same files).

Summary by CodeRabbit

  • New Features

    • Draft and workflow chat thread IDs now persist across reloads for each user and flow, helping conversations resume more reliably.
    • Workflow chat now shows cleaner message history by hiding interim agent messages.
  • Bug Fixes

    • Recovered better when a saved thread can’t be found, automatically starting a fresh thread instead of failing repeatedly.
    • Improved message history loading so locally added messages are preserved and duplicates are avoided.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a88eb06c-5a59-4c24-a9f5-00bd5f1e3a1d

📥 Commits

Reviewing files that changed from the base of the PR and between 4894b9f and 1085ffe.

📒 Files selected for processing (6)
  • app/src/components/flows/workflowCopilotThreads.test.ts
  • app/src/components/flows/workflowCopilotThreads.ts
  • app/src/hooks/useWorkflowBuilderChat.test.ts
  • app/src/hooks/useWorkflowBuilderChat.ts
  • app/src/store/__tests__/threadSlice.test.ts
  • app/src/store/threadSlice.ts

📝 Walkthrough

Walkthrough

Copilot thread ID caching moves from an in-memory Map to user-scoped localStorage with error handling. useWorkflowBuilderChat adds displayMessages (filtering interim agent messages) and rehydrates persisted threads via seedThreadId, recovering from stale/not-found threads. threadSlice merges local and fetched messages and evicts stale thread state on ThreadNotFound errors.

Changes

Copilot transcript filtering and thread recovery

Layer / File(s) Summary
Merge loaded thread messages and clear stale state
app/src/store/threadSlice.ts, app/src/store/__tests__/threadSlice.test.ts
loadThreadMessages evicts stale thread state and rejects with THREAD_NOT_FOUND_MESSAGE on ThreadNotFound errors, and its fulfilled reducer merges locally-only cached messages with fetched ones, sorted by createdAt.
Derive displayMessages and rehydrate seeded threads
app/src/hooks/useWorkflowBuilderChat.ts, app/src/hooks/useWorkflowBuilderChat.test.ts
Hook computes displayMessages by filtering out interim agent messages, rehydrates persisted threads via seedThreadId (skipping threads it created itself), and clears threadId on THREAD_NOT_FOUND_MESSAGE during send or rehydration.
Persist copilot thread ids in localStorage
app/src/components/flows/workflowCopilotThreads.ts, app/src/components/flows/workflowCopilotThreads.test.ts
Replaces the in-memory Map cache with user-scoped localStorage keys for get/set/remove of copilot thread ids, swallowing storage exceptions.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant useWorkflowBuilderChat
  participant threadSlice
  participant ChatRuntimeCore

  useWorkflowBuilderChat->>useWorkflowBuilderChat: derive displayMessages from messages
  useWorkflowBuilderChat->>threadSlice: loadThreadMessages(seedThreadId)
  useWorkflowBuilderChat->>ChatRuntimeCore: fetchAndHydrateTurnState(threadId)
  useWorkflowBuilderChat->>ChatRuntimeCore: fetchAndHydrateTurnHistory(threadId)
  threadSlice-->>useWorkflowBuilderChat: merged messages or THREAD_NOT_FOUND_MESSAGE
Loading

Possibly related PRs

  • tinyhumansai/openhuman#4578: Both PRs modify useWorkflowBuilderChat.ts's send/rehydration behavior around the dedicated builder thread.

Suggested labels: bug

Suggested reviewers: M3gA-Mind

Poem

A rabbit hops through storage lanes,
Threads once lost now stick like grains 🥕
No more "not found" to make us fret—
Stale threads evicted, fresh ones set.
displayMessages hide the noise,
Clean chats now for all my joys!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: persisting and rehydrating the Copilot chat across reloads.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8ffef9d0ba

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

export function getCopilotThreadId(flowId: string | null): string | null {
return copilotThreadByFlow.get(copilotThreadKey(flowId)) ?? null;
try {
return window.localStorage.getItem(storageKey(flowId));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle stale persisted copilot thread ids

When a cached id points at a thread that no longer exists (for example the workflow-builder thread was deleted/purged), this now returns that stale seed on every open. useWorkflowBuilderChat.send only creates a new thread when threadId is null, so the panel keeps trying to append to the missing thread and shows an error instead of recovering; clear the cache/null out the seed on a not-found rehydrate or append failure so the next send can create a fresh builder thread.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 51a6e23 + 42cb9be.

Root cause confirmed: useWorkflowBuilderChat's local threadId state never reset when the seeded thread was gone, so the panel kept retrying the dead thread on every send (and every rehydrate).

  • 51a6e23 (threadSlice.ts): loadThreadMessages now detects ThreadNotFound the same way addMessageLocal/addInferenceResponse/generateThreadTitleIfNeeded already do — evicts the stale thread from Redux and rejects with THREAD_NOT_FOUND_MESSAGE instead of just recording messagesError with no cleanup.
  • 42cb9be (useWorkflowBuilderChat.ts): on that rejection — whether it surfaces during the mount rehydrate or during a send()'s addMessageLocal append — the hook now nulls out its threadId. WorkflowCopilotPanel already reports every threadId change back up via onThreadIdChange, so this cascades into FlowCanvasPage clearing the stale workflowCopilotThreads.ts cache entry, and the next send creates a fresh builder thread instead of erroring against the dead one.

Added coverage in useWorkflowBuilderChat.test.ts (both the rehydrate-clears-threadId and send-clears-threadId paths) and threadSlice.test.ts (the new ThreadNotFound branch of loadThreadMessages).

@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

🧹 Nitpick comments (1)
app/src/components/flows/workflowCopilotThreads.test.ts (1)

63-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No coverage for setItem/removeItem failure paths.

Only getItem throwing is exercised; setCopilotThreadId's own try/catch (write and remove paths) is never triggered by a failing localStorage.setItem/removeItem, leaving that resilience branch unverified.

✅ Suggested additional test
+  it('degrades to a no-op when localStorage.setItem throws (write path)', () => {
+    const original = window.localStorage.setItem;
+    window.localStorage.setItem = () => {
+      throw new Error('quota exceeded');
+    };
+    try {
+      expect(() => setCopilotThreadId('flow-1', 'thread-abc')).not.toThrow();
+    } finally {
+      window.localStorage.setItem = original;
+    }
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/flows/workflowCopilotThreads.test.ts` around lines 63 -
75, The existing test only covers `getCopilotThreadId` when
`localStorage.getItem` throws, but it does not exercise the failure handling
inside `setCopilotThreadId`. Add coverage in `workflowCopilotThreads.test.ts`
that simulates `window.localStorage.setItem` and
`window.localStorage.removeItem` throwing, then verify `setCopilotThreadId`
still degrades to a no-op without throwing. Use the `setCopilotThreadId` and
`getCopilotThreadId` helpers to keep the test aligned with the resilience paths
in the implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/components/flows/workflowCopilotThreads.ts`:
- Around line 44-62: Add debug logging to the new localStorage persistence flow
so failures are observable: in `getCopilotThreadId` and `setCopilotThreadId`,
replace the silent `catch` blocks with calls to the existing `log(...)` helper
using a stable prefix similar to `useWorkflowBuilderChat`’s rehydration logging,
and include safe context like `flowId` and whether the operation is read or
write. Keep the error handling non-fatal, but make sure any quota/private-mode
or unexpected exception is logged before returning/nulling out so thread
restore/persist issues can be diagnosed.

In `@app/src/hooks/useWorkflowBuilderChat.ts`:
- Around line 186-213: The rehydrate effect in useWorkflowBuilderChat is still
able to overwrite an in-flight turn because it only skips threads created
locally via createdThreadIdRef, not existing seedThreadId values. Update the
rehydration logic around the useEffect that dispatches loadThreadMessages,
fetchAndHydrateTurnState, and fetchAndHydrateTurnHistory to also detect an
active send/turn-in-flight state and skip reloading until it settles, or change
the loadThreadMessages.fulfilled handling so it merges fetched messages into
messagesByThreadId[threadId] instead of replacing the array wholesale.

---

Nitpick comments:
In `@app/src/components/flows/workflowCopilotThreads.test.ts`:
- Around line 63-75: The existing test only covers `getCopilotThreadId` when
`localStorage.getItem` throws, but it does not exercise the failure handling
inside `setCopilotThreadId`. Add coverage in `workflowCopilotThreads.test.ts`
that simulates `window.localStorage.setItem` and
`window.localStorage.removeItem` throwing, then verify `setCopilotThreadId`
still degrades to a no-op without throwing. Use the `setCopilotThreadId` and
`getCopilotThreadId` helpers to keep the test aligned with the resilience paths
in the implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bdef21b4-7d1d-44c7-a3cd-b07f743ff148

📥 Commits

Reviewing files that changed from the base of the PR and between 67a6558 and 8ffef9d.

📒 Files selected for processing (7)
  • app/src/components/flows/WorkflowCopilotPanel.test.tsx
  • app/src/components/flows/WorkflowCopilotPanel.tsx
  • app/src/components/flows/workflowCopilotThreads.test.ts
  • app/src/components/flows/workflowCopilotThreads.ts
  • app/src/hooks/useWorkflowBuilderChat.test.ts
  • app/src/hooks/useWorkflowBuilderChat.ts
  • app/src/providers/ChatRuntimeProvider.tsx

Comment thread app/src/components/flows/workflowCopilotThreads.ts
Comment thread app/src/hooks/useWorkflowBuilderChat.ts
graycyrus added a commit to graycyrus/openhuman that referenced this pull request Jul 7, 2026
…l/replace

Two related threadSlice.ts gaps surfaced by PR tinyhumansai#4663 review:

- loadThreadMessages was the only read/write thunk in this file that did
  not special-case ThreadNotFound like addMessageLocal/addInferenceResponse/
  generateThreadTitleIfNeeded already do — a deleted/purged thread (e.g. a
  stale id cached by workflowCopilotThreads.ts) just recorded messagesError
  with no eviction, so the stale thread lingered in Redux forever. Now it
  evicts via the same evictStaleThread() path and rejects with
  THREAD_NOT_FOUND_MESSAGE.
- loadThreadMessages.fulfilled wholesale-replaced messagesByThreadId[id],
  which could clobber a message that landed locally (via a concurrent
  addMessageLocal/addInferenceResponse append) but predates this fetch's
  snapshot. useWorkflowBuilderChat's copilot rehydrate effect can race a
  WorkflowCopilotPanel auto-send (repairSeed/buildSeed) on the very same
  mount, so this is reachable, not just theoretical. Merge the fetched
  list with any local-only ids instead of replacing.

(addresses @coderabbitai on app/src/hooks/useWorkflowBuilderChat.ts:213,
foundation for the stale-thread-id fix on workflowCopilotThreads.ts:46)
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 7, 2026
@graycyrus

Copy link
Copy Markdown
Contributor Author

Addressed the nitpick from the CHANGES_REQUESTED review (workflowCopilotThreads.test.ts:63-75 — no coverage for setItem/removeItem failure paths) in 4894b9f. It wasn't posted as its own inline thread (only embedded in the review body), so replying here instead: added two tests mirroring the existing getItem-throws case — one for the write path (setItem throwing) and one for the clear path (removeItem throwing) — both asserting setCopilotThreadId degrades to a no-op rather than throwing. All 27 tests in the file pass.

@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 (1)
app/src/store/threadSlice.ts (1)

115-129: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a debug log at the ThreadNotFound detection point.

Only the nested evictStaleThread refresh-failure path logs (and only under IS_DEV); the actual eviction decision — a materially "changed flow" for stale-thread recovery — has no log line with threadId as a correlation field.

🔍 Suggested addition
       if (isThreadNotFoundCoreRpcError(error, threadId)) {
+        if (IS_DEV) {
+          console.debug('[threadSlice] loadThreadMessages: thread not found, evicting', {
+            threadId,
+          });
+        }
         await evictStaleThread(threadId, dispatch);
         return rejectWithValue(THREAD_NOT_FOUND_MESSAGE);
       }

As per coding guidelines: "On new or changed flows, add verbose debug logging with stable prefixes, correlation fields, and no secrets or full PII; incomplete changes are those lacking logging."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/store/threadSlice.ts` around lines 115 - 129, Add a debug log in the
getThreadMessages catch block at the isThreadNotFoundCoreRpcError check before
calling evictStaleThread. The ThreadNotFound branch currently changes the
recovery flow without any log, so emit a verbose debug message with a stable
prefix and threadId as the correlation field, then keep the existing
evictStaleThread and rejectWithValue behavior unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/src/store/threadSlice.ts`:
- Around line 115-129: Add a debug log in the getThreadMessages catch block at
the isThreadNotFoundCoreRpcError check before calling evictStaleThread. The
ThreadNotFound branch currently changes the recovery flow without any log, so
emit a verbose debug message with a stable prefix and threadId as the
correlation field, then keep the existing evictStaleThread and rejectWithValue
behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5b4fa251-be88-4bf8-82e9-c7e95bec6729

📥 Commits

Reviewing files that changed from the base of the PR and between 8ffef9d and 4894b9f.

📒 Files selected for processing (6)
  • app/src/components/flows/workflowCopilotThreads.test.ts
  • app/src/components/flows/workflowCopilotThreads.ts
  • app/src/hooks/useWorkflowBuilderChat.test.ts
  • app/src/hooks/useWorkflowBuilderChat.ts
  • app/src/store/__tests__/threadSlice.test.ts
  • app/src/store/threadSlice.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/src/components/flows/workflowCopilotThreads.ts
  • app/src/components/flows/workflowCopilotThreads.test.ts
  • app/src/hooks/useWorkflowBuilderChat.ts

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 7, 2026
graycyrus added 5 commits July 8, 2026 00:54
The Copilot chat thread id was only cached in a module-level Map, and
useWorkflowBuilderChat never reloaded a thread's messages/turn state on
mount — so a reload (or a fresh app launch) always started the copilot
transcript from empty even though the thread itself is durable
server-side (threadApi persists every turn).

- workflowCopilotThreads.ts: back the flow -> thread-id cache with
  localStorage (namespaced `${userId}:copilot-thread:<flow>`, matching
  the existing per-user localStorage convention) instead of an in-memory
  Map, wrapped in try/catch so private-mode/quota errors degrade to a
  no-op rather than throwing.
- useWorkflowBuilderChat: on mount, when `seedThreadId` names a thread
  this hook did not just create itself via `send()` (tracked via
  `createdThreadIdRef`), dispatch `loadThreadMessages` +
  `fetchAndHydrateTurnState` + `fetchAndHydrateTurnHistory` to rehydrate
  the full transcript + turn state from the core. The `createdThreadIdRef`
  guard avoids racing an in-flight turn when a caller echoes a
  just-created thread id back in as `seedThreadId`.

Depends on B4 (#<B4 PR>) — rehydration reloads the FULL transcript
(including interim narration bubbles), so without B4's `displayMessages`
filter, those would render as bubbles again after a reload.

Split from tinyhumansai#4650.
…sses @coderabbitai on workflowCopilotThreads.ts:62)

getCopilotThreadId/setCopilotThreadId touch localStorage with no logging,
so a quota/private-mode failure (or a get/set miss) leaves no trace to
debug why a copilot thread failed to persist/restore.
…l/replace

Two related threadSlice.ts gaps surfaced by PR tinyhumansai#4663 review:

- loadThreadMessages was the only read/write thunk in this file that did
  not special-case ThreadNotFound like addMessageLocal/addInferenceResponse/
  generateThreadTitleIfNeeded already do — a deleted/purged thread (e.g. a
  stale id cached by workflowCopilotThreads.ts) just recorded messagesError
  with no eviction, so the stale thread lingered in Redux forever. Now it
  evicts via the same evictStaleThread() path and rejects with
  THREAD_NOT_FOUND_MESSAGE.
- loadThreadMessages.fulfilled wholesale-replaced messagesByThreadId[id],
  which could clobber a message that landed locally (via a concurrent
  addMessageLocal/addInferenceResponse append) but predates this fetch's
  snapshot. useWorkflowBuilderChat's copilot rehydrate effect can race a
  WorkflowCopilotPanel auto-send (repairSeed/buildSeed) on the very same
  mount, so this is reachable, not just theoretical. Merge the fetched
  list with any local-only ids instead of replacing.

(addresses @coderabbitai on app/src/hooks/useWorkflowBuilderChat.ts:213,
foundation for the stale-thread-id fix on workflowCopilotThreads.ts:46)
@chatgpt-codex-connector on workflowCopilotThreads.ts:46)

A cached thread id in workflowCopilotThreads.ts can point at a thread
that was deleted/purged since it was cached. useWorkflowBuilderChat.send
only creates a new thread when threadId is null, so the panel kept
retrying the missing thread and surfacing an error instead of recovering.

loadThreadMessages/addMessageLocal now reject with THREAD_NOT_FOUND_MESSAGE
for this case (previous commit) — null out this hook's threadId in
response, both on a not-found rehydrate (mount) and a not-found append
(send). WorkflowCopilotPanel already reports every threadId change back
up via onThreadIdChange, so this cascades into FlowCanvasPage clearing
the stale workflowCopilotThreads.ts cache entry too, letting the next
send create a fresh builder thread.
… cache (addresses @coderabbitai nitpick on workflowCopilotThreads.test.ts:63-75)

CodeRabbit flagged that only getItem's throw path was exercised; setCopilotThreadId's
own try/catch (write and remove) was never triggered by a failing setItem/removeItem.
@graycyrus graycyrus force-pushed the feat/flows-copilot-chat-persistence branch from 4894b9f to 1085ffe Compare July 7, 2026 19:24
@graycyrus graycyrus merged commit cd0b08e into tinyhumansai:main Jul 7, 2026
13 of 14 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant