windmill-labs/windmill · error

This page has changes that are not valid JSON. Fix them firs

Error message

This page has changes that are not valid JSON. Fix them first.

What it means

flushOrRefuse is the pre-flight gate before opening a session from the page drawer: it refuses to save if any editor on the page currently holds text that is not valid JSON. Throwing before flushing guarantees no stale or broken buffer is written and the session doesn't open on lost changes.

Source

Thrown at frontend/src/lib/components/sessions/pageDrawerSession.ts:50

		Object.entries(TRIGGER_PAGES).map(([kind, p]) => [
			p.path,
			`trigger_${kind as TriggerKind}` as UserDraftItemKind
		])
	)
}

// Push the drawer's pending autosave and refuse to leave if it did not land: `flush`
// reports a failed or conflicting POST through its state rather than by throwing, so
// routing regardless would open the preview on the server's older draft while the drawer
// the user is looking at still holds the edit. The flush is the explicit kind, saving even
// with auto-save off: asking for a session is asking for the edit to come along.
async function flushOrRefuse(query: Parameters<typeof UserDraftDbSyncer.flush>[0]): Promise<void> {
	// Before the save, not after: text that does not parse never reached the draft, so
	// leaving now would open the session on the last value that did and drop the buffer
	// with the drawer — and saving first would write that stale value on the way to
	// refusing. The editors were materialised before this call, so the check is current.
	if (anyEditorUnparseable()) {
		throw new Error('This page has changes that are not valid JSON. Fix them first.')
	}
	await UserDraftDbSyncer.flush(query)
	if (UserDraftDbSyncer.getConflict(query).conflict) {
		throw new Error(
			'This draft has a newer conflicting version on the server. Resolve it here before opening a session.'
		)
	}
	const { state, failureMessage } = UserDraftDbSyncer.getState(query)
	if (state === 'failed') {
		throw new Error(
			`Saving the latest draft failed (${failureMessage ?? 'unknown error'}). Retry before opening a session.`
		)
	}
}

// How each page addresses a row in its hash. Resources route theirs through an extra
// segment; every other page names the path directly.
const drawerHashFor = (pagePath: string, itemPath: string) =>

View on GitHub (pinned to e474e8803c)

Solutions

  1. Fix the JSON in the flagged editor until it parses, then retry
  2. Discard the invalid editor changes before opening the session
  3. Use the editor's format/lint indicator to locate the syntax error

Example fix

// before
{ "a": 1, }
// after
{ "a": 1 }
Defensive patterns

Strategy: validation

Validate before calling

if (anyEditorUnparseable()) return; // don't attempt to open a session
await openSession(query)

Type guard

function isParseableJson(s: string): boolean { try { JSON.parse(s); return true } catch { return false } }

Try / catch

try { await openSession(query) } catch (e) { if (/not valid JSON/.test(String(e))) focusFirstUnparseableEditor(); else throw e }

Prevention

When it happens

Trigger: Calling pageDrawerSession's open/flush path (via pageDrawerSessionSource) while anyEditorUnparseable() reports an editor with invalid JSON in its buffer.

Common situations: User typed a partial JSON edit (trailing comma, unclosed brace) in a resource/script arg editor and immediately clicked 'open session'; programmatically set editor text that wasn't validated.

Related errors


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