windmill-labs/windmill · error · Error

UserDraft: no workspace available (pass opts.workspace or se

Error message

UserDraft: no workspace available (pass opts.workspace or set $workspaceStore)

What it means

UserDraft's resolveWorkspace determines which workspace a draft belongs to. It takes opts.workspace, falling back to the $workspaceStore; if both are empty it throws because draft storage keys are workspace-scoped and cannot be computed.

Source

Thrown at frontend/src/lib/userDraft.svelte.ts:166

function rememberWrite(
	workspace: string,
	itemKind: UserDraftItemKind,
	path: string,
	val: unknown
): void {
	const mk = mapKey(workspace, itemKind, path)
	if (val === undefined) {
		writtenCache.delete(mk)
	} else {
		writtenCache.set(mk, { workspace, itemKind, path, val: snapshotDraftValue(val) })
	}
}

function resolveWorkspace(opts?: UserDraftOptions): string {
	const ws = opts?.workspace ?? get(workspaceStore)
	if (!ws) {
		throw new Error(
			'UserDraft: no workspace available (pass opts.workspace or set $workspaceStore)'
		)
	}
	return ws
}

function mapKey(workspace: string, itemKind: UserDraftItemKind, path: string): string {
	return `${workspace}/${itemKind}/${path}`
}

function liveEditorDraftKey(workspace: string, itemKind: UserDraftItemKind): string {
	return `${workspace}/${itemKind}`
}

function snapshotDraftValue<V>(value: V | undefined): V | undefined {
	if (value === undefined) return undefined
	try {
		return structuredClone($state.snapshot(value)) as V

View on GitHub (pinned to e474e8803c)

Solutions

  1. Pass the workspace explicitly: UserDraft.set(itemKind, path, value, { workspace: ws })
  2. Ensure the call happens after app initialization so $workspaceStore is populated
  3. Guard the call site: skip draft operations when the workspace is falsy
  4. In tests, seed the workspace store (or pass opts.workspace) before invoking UserDraft

Example fix

// before
UserDraft.set('script', path, draft) // throws if store not ready
// after
const ws = get(workspaceStore)
if (ws) {
  UserDraft.set('script', path, draft, { workspace: ws })
}
Defensive patterns

Strategy: validation

Validate before calling

import { get } from 'svelte/store'
import { workspaceStore } from '$lib/stores'

function ensureWorkspace(opts?: { workspace?: string }): string {
  const ws = opts?.workspace ?? get(workspaceStore)
  if (!ws) throw new Error('UserDraft called before workspace was set')
  return ws
}
// call ensureWorkspace() before any UserDraft API

Type guard

function hasWorkspace(opts?: { workspace?: string }): opts is { workspace: string } {
  return typeof opts?.workspace === 'string' && opts.workspace.length > 0
}

Try / catch

try {
  UserDraft.set(itemKind, path, value)
} catch (err) {
  if (err instanceof Error && err.message.includes('no workspace available')) {
    return // app not initialized or logged out; skip draft persistence
  }
  throw err
}

Prevention

When it happens

Trigger: Calling any UserDraft API (get/set/remove/ws helpers) before the workspace store is initialized, or outside a component/context where $workspaceStore is set, without passing { workspace } in opts.

Common situations: Module-level or early-lifecycle calls running before app init; usage in tests or stories without the workspace store mocked; workspace switched/cleared during logout while a draft operation is in flight; calling from a non-Svelte utility module where the store was never loaded.

Related errors


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