windmill-labs/windmill · error
the session workspace could not be created or committed (for
Error message
the session workspace could not be created or committed (fork creation may have failed)
What it means
createRuntime in sessionRuntime.svelte.ts throws this when a staged-new-fork session draft could not be materialised into a real workspace. materializeFork is designed to toast and return undefined instead of throwing, so the undefined return is converted into a throw here. Throwing is deliberate: it makes AIChatManager.sendRequest abort so the message and its tool calls are not shipped to the parent workspace while the pending fork is silently dropped.
Source
Thrown at frontend/src/lib/components/sessions/sessionRuntime.svelte.ts:416
// session targeting the right workspace. Both calls are idempotent.
manager.beforeSend = async () => {
materializeTransient(session.id)
// Session is now persisted → flush any linked files buffered while it was transient.
await manager.attachedFiles.flushPending()
// Fork creation is the slow part of the pre-flight; label the loading
// indicator so the user knows why the send is taking a moment.
manager.loadingLabel = 'Creating workspace fork...'
const committed = await commitSessionWorkspace(session.id, get(workspaceStore) ?? undefined)
manager.loadingLabel = undefined
// commitSessionWorkspace returns undefined only when the session did NOT
// commit to a workspace — most importantly when a staged fork failed to
// materialise (materializeFork is built to toast + return undefined rather
// than throw). Throwing here is what makes AIChatManager.sendRequest abort:
// otherwise the send proceeds against get(workspaceStore) (the parent for a
// staged-new-fork draft), shipping the message + its tool calls to the
// wrong workspace while the pending_fork is silently dropped.
if (!committed) {
throw new Error(
'the session workspace could not be created or committed (fork creation may have failed)'
)
}
// The composer may have been enabled by a previous workspace's copilot
// config (copilotInfo is global). getCurrentModel() reads it when the
// request builds just after this hook, so load the committed workspace's
// config first — otherwise a send right after switching workspaces could
// pick the old provider/model while the proxy + tools target the new one.
// SessionWrapper's active-session load usually already did this; skip the
// fetch when it matches (a staged fork commits to a fresh id its load missed).
if (get(copilotWorkspace) !== committed) {
await loadCopilot(committed)
}
}
manager.afterFirstTurnSaved = () => generateAndApplySessionSummary(session.id, manager)
// One cell per (kind, path). Created on demand by the load methods; each holds
// cached content (KB–MB), not a mounted editor. Bounded to the items openView on GitHub (pinned to e474e8803c)
Solutions
- Inspect the earlier toast — materializeFork toasts the underlying backend rejection; fix that cause first (e.g. workspace-limit or backend error).
- Retry sending after the fork creation issue is resolved, or switch the session to an existing (non-fork) workspace.
- Check network/backend logs around the fork-creation API call for the root failure.
- If this reproduces on every send, verify the session's pending_fork payload is valid (parent workspace, name) before calling sendRequest.
Example fix
// before
const runtime = await getOrCreateRuntime(session) // throws
// after
try {
const runtime = await getOrCreateRuntime(session)
} catch {
// fork materialization failed: surface toast, abort send,
// let the user pick a non-fork workspace or retry
setSessionPendingWorkspace(session.id, null)
} Defensive patterns
Strategy: try-catch
Validate before calling
const committed = await materializeFork(fork)
if (!committed) throw new Error('fork creation failed — aborting send') Type guard
function isCommitted(id: string | undefined | null): id is string {
return typeof id === 'string' && id.length > 0
} Try / catch
try {
const runtime = await getOrCreateRuntime(session)
await manager.sendRequest(runtime.workspaceId, message)
} catch (e) {
if (String(e).includes('session workspace could not be created')) {
toast.error('Fork creation failed — message not sent. Pick a workspace or retry.')
return // do NOT fall back to the parent workspace
}
throw e
} Prevention
- Never send chat requests against get(workspaceStore) for staged fork drafts — always go through the runtime
- Handle materializeFork's undefined return at the call site instead of assuming success
- Watch for the fork-failure toast and resolve its root cause (workspace limit, backend error) before re-sending
When it happens
Trigger: Calling getOrCreateRuntime/createRuntime for a session whose pending fork fails to materialize — typically because the backend rejected fork creation (workspace limit, backend error, license check) — so materializeFork returns undefined and `committed` is falsy.
Common situations: Sending an AI chat message from a session that is a staged fork draft; the backend rejects fork creation (e.g. CE workspace limit or transient backend failure), and the user's message would otherwise go to the wrong workspace.
Related errors
- Workspace '${forkWorkspaceId}' is not a fork (no parent_work
- No git-sync repositories configured in workspace
- No workspace available
- failed to read ${kind} ${path} in ${workspace}
- No workspace selected
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/9dfe5f15f25033e8.
Report an issue: GitHub.