windmill-labs/windmill · error

local script ${path} has no content/language

Error message

local script ${path} has no content/language

What it means

Before launching a local (workspace-resident) pipeline script during a cascade run, makeLaunch checks that the script has both `content` and `language`. It throws this error when a locally resolved script came back from the workspace without usable body or language, since runScriptPreview requires both fields.

Source

Thrown at frontend/src/lib/components/assets/AssetGraph/cascadeRun.ts:86

	tempScriptRefs?: Record<string, string>
	// Extra run args for a specific node (e.g. the uploaded S3Object bound to a
	// `data_upload` cascade root). Merged over `_wmill_skip_asset_dispatch`; all
	// other nodes run with empty inputs as before.
	argsFor?: (path: string) => Record<string, any> | undefined
	onLaunched?: (path: string, jobId: string) => void
}): (path: string) => Promise<string> {
	return async function launch(path: string): Promise<string> {
		const local = opts.resolveLocal?.(path)
		// Caller args (e.g. the run form for the cascade root) must NOT be able to
		// re-enable backend asset dispatch while the client orchestrates the closure
		// — that would double-run downstream / run deployed subscribers. Drop any
		// `_wmill_skip_asset_dispatch` a caller supplied, and always spread it LAST.
		const { _wmill_skip_asset_dispatch: _reserved, ...extra } = opts.argsFor?.(path) ?? {}
		void _reserved
		let jobId: string
		if (local) {
			if (!local.content || !local.language) {
				throw new Error(`local script ${path} has no content/language`)
			}
			jobId = await JobService.runScriptPreview({
				workspace: opts.workspace,
				requestBody: {
					content: local.content,
					language: local.language,
					path,
					args: { ...extra, _wmill_skip_asset_dispatch: true },
					...(local.tag ? { tag: local.tag } : {}),
					...(opts.tempScriptRefs ? { temp_script_refs: opts.tempScriptRefs } : {})
				}
			})
		} else {
			jobId = await JobService.runScriptByPath({
				workspace: opts.workspace,
				path,
				requestBody: { ...extra, _wmill_skip_asset_dispatch: true }
			})

View on GitHub (pinned to e474e8803c)

Solutions

  1. Open the script at that path in the Windmill hub and re-save/deploy it with real content and a language
  2. Re-fetch the script with content included (ensure the resolver requests full script data, not metadata-only)
  3. If the path is wrong, correct the asset/dependency reference to point at the deployed script
  4. For generated drafts, populate `content` and `language` before dispatching the cascade run

Example fix

// before
const local = await resolveLocal(path) // { path } only — no content
await launch(path)
// after
const local = await getScriptByPath({ workspace, path }) // full content + language
if (!local?.content || !local?.language) throw new Error(`script ${path} missing on workspace`)
await launch(path)
Defensive patterns

Strategy: validation

Validate before calling

const local = await resolveLocal(path)
if (!local?.content?.trim() || !local?.language) {
  throw new Error(`local script ${path} is empty or has no language; redeploy it before running the cascade`)
}

Type guard

function isRunnableScript(s: { content?: string | null; language?: string | null } | null): s is { content: string; language: string } {
  return !!s && typeof s.content === 'string' && s.content.trim().length > 0 && typeof s.language === 'string'
}

Try / catch

try {
  await launch(path)
} catch (e) {
  if (e instanceof Error && e.message.includes('has no content/language')) {
    const fresh = await getScriptByPath({ workspace, path }) // re-fetch full content
    if (!fresh?.content || !fresh?.language) throw new Error(`script ${path} missing on workspace; fix the asset reference`)
    return launch(path, fresh)
  }
  throw e
}

Prevention

When it happens

Trigger: `resolveLocal(path)` returned a script object with empty/undefined `content` or `language` — e.g. the script exists but its deployment metadata was fetched instead of full content, the script is a draft/empty stub, or the resolver silently returned a skeleton object for a missing path.

Common situations: Script deployed from a flow without content; script fetched with `include_content` off or a partial get; asset graph pointing at a node whose underlying script was deleted or emptied; language not persisted for very old scripts or manually inserted DB rows.

Related errors


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