tinyhumansai/openhuman · error

Runtime unavailable: ${runtime.runtime} (${runtime.error ??

Error message

Runtime unavailable: ${runtime.runtime} (${runtime.error ?? 'unavailable'})

What it means

Before running a workflow, the runner infers the managed runtimes it needs (inferRuntimeRequirement) and asks the core via skillsApi.resolveRuntimes; every runtime reporting available=false is joined into one error naming the runtime and its reason (runtime.error, defaulting to 'unavailable').

Source

Thrown at app/src/components/skills/WorkflowRunnerBody.tsx:566

    try {
      await skillsApi.cancelRun(runId);
    } catch (err) {
      log('cancelRun error: %s', err instanceof Error ? err.message : String(err));
    }
    setRecentRunsRefreshNonce(n => n + 1);
  }, []);

  const ensureRuntimeAvailability = useCallback(async () => {
    const runtimeRequirement = inferRuntimeRequirement(selectedWorkflow);
    if (!runtimeRequirement) return;

    const resolved = await skillsApi.resolveRuntimes(runtimeRequirement);
    const unavailable = resolved.runtimes.filter(runtime => !runtime.available);
    if (unavailable.length === 0) return;

    const prefix = t('settings.skillsRunner.error.runtimeUnavailable', 'Runtime unavailable');
    const defaultReason = t('settings.skillsRunner.error.runtimeUnavailableDefault', 'unavailable');
    throw new Error(
      unavailable
        .map(runtime => `${prefix}: ${runtime.runtime} (${runtime.error ?? defaultReason})`)
        .join('; ')
    );
  }, [selectedWorkflow, t]);

  const handleRun = useCallback(async () => {
    if (!description) return;
    // Re-entry guard: a second click before React applies the disabled state
    // would otherwise fire `skill_runtime_run` twice and spawn two real runs.
    if (runSubmitGuardRef.current) {
      log('runWorkflow: ignoring re-entrant click while a run is starting');
      return;
    }
    if (missingRequired.length > 0) {
      setRun({
        status: 'error',
        message: `${t('settings.skillsRunner.error.missingRequired')} ${missingRequired.join(', ')}`,

View on GitHub (pinned to a221052e0d)

Solutions

  1. Install/enable the named runtime from Settings (skills runner / runtimes) and retry
  2. Read the parenthesised reason: 'disabled' means flip the config flag; a download error means retry the runtime install
  3. Re-run the workflow only after resolveRuntimes reports the requirement available
Defensive patterns

Strategy: validation

Validate before calling

// Resolve runtimes when the workflow is selected, not at run time:
const req = inferRuntimeRequirement(selectedWorkflow);
if (req) {
  const resolved = await skillsApi.resolveRuntimes(req);
  const missing = resolved.runtimes.filter(r => !r.available);
  if (missing.length) { /* disable Run and show install hint with r.error */ }

Type guard

type ResolvedRuntime = { runtime: string; available: boolean; error?: string };
const missingRuntimes = (rs: ResolvedRuntime[]): ResolvedRuntime[] =>
  rs.filter(r => !r.available);

Try / catch

try {
  await ensureRuntimeAvailability();
} catch (e) {
  if (e instanceof Error && e.message.includes('Runtime unavailable')) {
    // parse the 'name (reason)' pairs and link each to its Settings install flow
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Workflow uses node_exec/npm_exec or Python steps while the managed Node/Python runtime is not installed, disabled in config, or failed to download — resolveRuntimes marks it unavailable with the failure reason.

Common situations: Fresh install where the node_runtime harness-init step has not downloaded the toolchain yet; runtime disabled via config (node.enabled=false); a partial runtime download after an interrupted install.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/b0aa65c87fdab476. Report an issue: GitHub.