windmill-labs/windmill · error

GET /assets/graph → ${res.status}

Error message

GET /assets/graph → ${res.status}

What it means

The pipeline graph loader fetches GET /w/{ws}/assets/graph and, on any non-OK HTTP response, throws this error embedding the status code. It is the app's own guard so the failed fetch surfaces instead of a confusing JSON parse error on an error body.

Source

Thrown at frontend/src/routes/(root)/(logged)/pipeline/[folder]/+page.svelte:2238

		if (g.runnables.length === 0 && g.assets.length === 0) {
			setMode('edit', { replace: true })
		}
	})

	let graphRes = resource(
		[() => $workspaceStore, () => folder],
		async ([ws, f], _prev, { signal }) => {
			if (!ws || !f) return undefined
			const base_url = OpenAPI.BASE ?? ''
			const params = new URLSearchParams({
				folder: f,
				asset_kinds: DATA_KINDS.join(',')
			})
			const res = await fetch(`${base_url}/w/${ws}/assets/graph?${params}`, {
				credentials: 'include',
				signal
			})
			if (!res.ok) throw new Error(`GET /assets/graph → ${res.status}`)
			return hideDbtRunnables((await res.json()) as AssetGraphResponse)
		}
	)

	// Folder whose graph is actually rendered. `graphRes.current` is stale-
	// while-revalidate on an in-place folder switch, so keying the canvas's
	// one-shot initial fit on the route param would fire the new folder's fit
	// on the old graph and leave the fresh one unfitted. `folder` is read
	// untracked: the key must move only when a graph lands.
	let viewportFitFolder = $state('')
	$effect(() => {
		if (graphRes.current) untrack(() => (viewportFitFolder = folder))
	})

	// Body / inferred-assets prefetch sweep. Watches `g.runnables`; for any
	// non-draft path we haven't fetched yet, fetches `getScriptByPath` and
	// `inferAssets`, and stores both in their respective only-add caches.
	// All three previously-sticky maps (`inferredWritesByPath`,

View on GitHub (pinned to e474e8803c)

Solutions

  1. Read the status in the message: 401 → re-login, 403 → request workspace access, 500 → check backend logs for the graph endpoint
  2. Refresh the page / re-authenticate and retry the fetch
  3. Check backend logs around /assets/graph for graph-computation failures
  4. Reduce graph size (filter folders/kinds) if the backend times out on huge graphs

Example fix

// before
if (!res.ok) throw new Error(`GET /assets/graph → ${res.status}`)
// after
if (!res.ok) {
  const body = await res.text().catch(() => '')
  throw new Error(`GET /assets/graph → ${res.status}: ${body.slice(0, 200)}`)
}
Defensive patterns

Strategy: try-catch

Validate before calling

const res = await fetch(url, { credentials: 'include', signal })
if (!res.ok) {
  if (res.status === 401) await reLogin()
  if (res.status === 403) throw new Error('Missing workspace access for /assets/graph')
}

Type guard

function isOk(res: Response): res is Response & { ok: true } { return res.ok }

Try / catch

try {
  const graph = await loadAssetGraph(ws, signal)
} catch (e) {
  const m = /→ (\d{3})/.exec(e.message)
  if (m?.[1] === '401') redirectToLogin()
  else sendUserToast(`Failed to load asset graph${m ? ` (HTTP ${m[1]})` : ''}`, true)
}

Prevention

When it happens

Trigger: Any request to /assets/graph (with asset_kinds=DATA_KINDS) that returns 4xx/5xx: expired session cookie, missing permissions on the workspace, malformed query params, or backend error while computing the dependency graph.

Common situations: Session expired while the pipeline tab was open (401); user lacks access to the workspace/folder (403); backend timeout or DB error computing a large asset graph (500); reverse proxy returning 502.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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