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 open

View on GitHub (pinned to e474e8803c)

Solutions

  1. Inspect the earlier toast — materializeFork toasts the underlying backend rejection; fix that cause first (e.g. workspace-limit or backend error).
  2. Retry sending after the fork creation issue is resolved, or switch the session to an existing (non-fork) workspace.
  3. Check network/backend logs around the fork-creation API call for the root failure.
  4. 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

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


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/9dfe5f15f25033e8. Report an issue: GitHub.