windmill-labs/windmill · error

Workspace "${workspace}" is not a fork — it has no parent wo

Error message

Workspace "${workspace}" is not a fork — it has no parent workspace to compare against. Use diff without 'against' to compare drafts vs deployed versions.

What it means

The copilot diff tool's `against: 'parent'` mode compares a fork's deployed items against its parent workspace. `forkParentOrThrow` resolves the fork's parent id; if the workspace has none (it is not a fork), the tool throws this error explaining that `against` is fork-only and draft-vs-deployed diffing is the alternative. It is a mode-eligibility guard.

Source

Thrown at frontend/src/lib/components/copilot/chat/global/core.ts:6834

				? `No drafts match your filters (${index.entries.length} draft(s) exist in the workspace).`
				: 'No drafts in this workspace — nothing differs from the deployed state.'
			: `${total} draft(s) vs deployed:`
	const result = [summaryLine, ...lines, ...notes].join('\n')
	toolCallbacks.setToolStatus(toolId, {
		content: `Workspace diff: ${total} draft(s)`,
		result
	})
	return result
}

function forkComparisonUnavailableMessage(parent: string): string {
	return `The comparison with parent workspace "${parent}" is unavailable for this fork (created before comparison tracking existed).`
}

function forkParentOrThrow(workspace: string): string {
	const parent = getForkParentWorkspaceId(workspace)
	if (!parent) {
		throw new Error(
			`Workspace "${workspace}" is not a fork — it has no parent workspace to compare against. Use diff without 'against' to compare drafts vs deployed versions.`
		)
	}
	return parent
}

function forkEntryLabel(e: ForkDiffEntryView): string {
	const label = e.type === 'trigger' ? `${e.triggerKind} trigger` : (e.type ?? e.kind)
	return `${label} "${e.path}"`
}

function formatForkIndexEntry(e: ForkDiffEntryView): string {
	const name = forkEntryLabel(e)
	const draftFlag = e.hasLocalDraft ? ' [+ local draft]' : ''
	const aheadBehind = [
		e.ahead > 0 ? `ahead ${e.ahead}` : undefined,
		e.behind > 0 ? `behind ${e.behind}` : undefined
	]

View on GitHub (pinned to e474e8803c)

Solutions

  1. Drop the `against` argument to compare drafts vs deployed versions in the current workspace
  2. Switch to the actual fork workspace before using `against: 'parent'`
  3. To compare arbitrary workspaces, use the workspace UI/CLI rather than this tool

Example fix

// before
diff({ type: 'script', path: 'f/build', against: 'parent' }) // not a fork
// after
diff({ type: 'script', path: 'f/build' }) // draft vs deployed
Defensive patterns

Strategy: validation

Validate before calling

const parent = getForkParentWorkspaceId(workspace)
if (!parent) throw new Error(`${workspace} is not a fork; use diff without 'against'`

Type guard

function isFork(workspace: string): boolean {
  return getForkParentWorkspaceId(workspace) != null
}

Try / catch

try {
  return await diffItem({ type, path, against: 'parent' })
} catch (e) {
  if (String(e?.message).includes('is not a fork')) {
    return await diffItem({ type, path }) // fall back to draft vs deployed
  }
  throw e
}

Prevention

When it happens

Trigger: Calling diff with `against: 'parent'` from a workspace that is not a fork (a normal top-level workspace), so `getForkParentWorkspaceId` returns falsy.

Common situations: Agents assuming every workspace has a parent; users trying to compare two arbitrary workspaces via `against`; running the fork-comparison flow in the original workspace instead of the fork.

Related errors


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