windmill-labs/windmill · error · ArtifactPersistenceError

ArtifactPersistenceError

Error message

ArtifactPersistenceError

What it means

ArtifactPersistenceError is thrown by artifactsState.create when a plan-role artifact was created in memory but mutateArtifact did not report outcome 'saved' — i.e. the IndexedDB write failed or was unavailable. For ordinary artifacts an unpersisted result degrades silently, but a plan cannot: returning one the database refused would let the user approve a plan that vanishes on reload, so it is raised instead.

Source

Thrown at frontend/src/lib/components/copilot/chat/artifacts/artifactsState.svelte.ts:183

			updatedAt: now,
			version: 1
		})
		// A plan's id is the session's, so a second one cannot be minted; the slot check happens
		// on the row this write is about to replace, inside the transaction that replaces it.
		const id = input.role === 'plan' ? planArtifactId(sessionId) : randomUUID()
		const { outcome, artifact } = await mutateArtifact(id, (existing) => {
			// Before the slot check: a posture that may not mint a plan at all is the more useful
			// thing to say, and it holds whether or not the session already has one.
			if (input.role === 'plan' && opts?.canWritePlan?.() === false) {
				throw new PlanWriteRefusedError()
			}
			if (existing) throw new PlanSlotTakenError(existing)
			const created = draft(id)
			return { artifact: created, snapshots: [snapshotOf(created, 1)] }
		})
		// An ordinary artifact degrades unpersisted; a plan cannot. Returning one the database
		// refused would let the user approve a plan that disappears on reload.
		if (input.role === 'plan' && outcome !== 'saved') throw new ArtifactPersistenceError()
		const written = artifact ?? draft(id)
		this.#reflect(written)
		return written
	}

	/**
	 * Merge changes into an existing artifact. Returns undefined if `id` is unknown, or if
	 * `opts.sessionId` is given and the artifact belongs to a different session.
	 */
	async update(
		id: string,
		input: UpdateArtifactInput,
		opts?: { sessionId?: string; canWritePlan?: () => boolean }
	): Promise<PersistedArtifact | undefined> {
		let refused = false
		const { outcome, artifact } = await mutateArtifact(id, (stored) => {
			// Read inside the mutator: hoisted out, it would weigh a stale copy against a fresh one.
			const held = this.artifacts.find((a) => a.id === id)

View on GitHub (pinned to e474e8803c)

Solutions

  1. Check that IndexedDB is writable (repeat the write or inspect getDB()/console errors) and retry the plan creation after storage recovers
  2. Free storage or reduce plan content if quota exceeded triggered the failed save
  3. Handle ArtifactPersistenceError in the tool wrapper so the model is told the plan was not saved instead of presenting an unsavable plan to the user for approval

Example fix

// before
const plan = await artifacts.create({ role: 'plan', sessionId, content })
// after
let plan
try { plan = await artifacts.create({ role: 'plan', sessionId, content }) }
catch (e) {
  if (e instanceof ArtifactPersistenceError) { notifyPlanNotSaved(); return }
  throw e
}
Defensive patterns

Strategy: try-catch

Validate before calling

const db = await getDB()
if (!db) throw new Error('Cannot create plan: artifact store unavailable')

Type guard

function isArtifactPersistenceError(e: unknown): e is ArtifactPersistenceError {
  return e instanceof ArtifactPersistenceError
}

Try / catch

try {
  await artifacts.create({ role: 'plan', sessionId, content })
} catch (e) {
  if (e instanceof ArtifactPersistenceError) {
    toolCallbacks.report('Plan could not be saved; do not ask the user to approve it')
    return
  }
  throw e
}

Prevention

When it happens

Trigger: Creating a plan artifact in a browser context where IndexedDB is unavailable or the transaction fails (quota exceeded, DB closed, private mode), so mutateArtifact returns outcome other than 'saved'.

Common situations: Private-browsing storage restrictions; IndexedDB quota exceeded by large plan documents; browser killing the DB mid-session; extensions blocking storage writes.

Related errors


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