Files
twenty/packages/twenty-server
Félix Malfait c35e364562 Fix AI campaign editing: tool auth context in workers and live editor resync (#23811)
Fixes two issues reported when creating an email campaign through the AI
chat panel.

## 1. `save_campaign` failed with "Workspace auth context not set"

The AI chat streams inside a queue worker job, where no HTTP middleware
populates the async-local workspace auth context. The new
`MessageCampaignDraftService.saveDraft()` relies on
`executeInWorkspaceContext()`'s fallback to `getWorkspaceAuthContext()`,
which throws outside HTTP requests. Database CRUD tools worked because
`dispatchDatabaseCrud` builds an auth context explicitly; static tools
had no equivalent.

**Fix:** `ToolExecutorService.dispatch` (the single choke point for all
tool executions: preloaded chat tools, `execute_tool`, MCP, workflow
agents) now resolves the acting identity once, reusing a provided auth
context or building a user context from `userId`/`userWorkspaceId`, and
runs the dispatch inside `withWorkspaceAuthContext()`. This mirrors what
`WorkspaceAuthContextMiddleware` does for HTTP requests, so tool code
can rely on the async-local context on every transport.

Side benefit: metadata tools executed from chat previously emitted
metadata events with no user attribution (`MetadataEventEmitter`
swallows the missing context); they are now attributed correctly.

## 2. AI changes to the open campaign required a page refresh

The SSE pipeline delivers worker-originated record updates to the Apollo
cache correctly. The campaign editor ignored them:
`usePersistedCampaignDraft` seeds local draft state from the record
once, and the subject/body/list inputs are uncontrolled (TipTap reads
`defaultValue` on mount only).

**Fix:** the draft hook now adopts upstream record values while the
draft is pristine and exposes a `draftResyncKey` that remounts the
`defaultValue`-seeded inputs. Unsaved local edits win over concurrent
remote changes (last write wins on flush), and echoes of our own
debounced persists never remount inputs mid-typing.

## Tests

- `tool-executor.service.spec.ts`: auth context exposed to static tools,
provided-context reuse, no-identity passthrough, no context leakage
after dispatch, CRUD receives the resolved context, CRUD still rejects
without identity.
- `usePersistedCampaignDraft.test.tsx`: adopt-when-pristine, own-echo
stability, dirty-draft-wins, adopt-after-persist.
- `lint:diff-with-main` and `typecheck` clean on both packages.

---
_Generated by [Claude
Code](https://claude.ai/code/session_018nGvGhFahw1pcefb3P4iCk)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23811?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-08-05 20:27:21 +02:00
..