Hmbown/CodeWhale · error · Error

${name} is unavailable in Workflow scripts: runs must be det

Error message

${name} is unavailable in Workflow scripts: runs must be deterministic for record/replay

What it means

Thrown by NativeMemoryStore::scope_path when a memory operation is scoped to MemoryScope::Workspace but workspace_id is None. Workspace memory is stored per-repository under root/workspace/<id>/MEMORY.md, so an identifier is mandatory to pick the directory. The id is normally derived by hashing the git remote.origin.url (NativeMemoryStore::workspace_id), which returns None for non-git directories or repos without an origin remote.

Source

Thrown at crates/workflow-js/src/vm.rs:1033

        "explore" | "explorer" | "scout" | "plan" | "planner" | "review" | "reviewer"
        | "verify" | "verifier" => Some(TaskRoleKind::ReadOnly),
        "general" | "worker" => Some(TaskRoleKind::General),
        "implement" | "implementer" | "builder" => Some(TaskRoleKind::Implementer),
        _ => None,
    }
}

/// The JS prelude injected before every script: determinism bans, the
/// `task`/`parallel`/`pipeline`/`log`/`phase` stdlib (design §7), and the
/// `budget` global.
fn prelude() -> String {
    PRELUDE_TEMPLATE.replace("__MAX_ITEMS__", &PARALLEL_MAX_ITEMS.to_string())
}

const PRELUDE_TEMPLATE: &str = r#""use strict";
(() => {
  const banned = (name) => () => {
    throw new Error(name + " is unavailable in Workflow scripts: runs must be deterministic for record/replay");
  };
  const BannedDate = function Date() {
    throw new Error("new Date()/Date() is unavailable in Workflow scripts: runs must be deterministic for record/replay");
  };
  BannedDate.now = banned("Date.now()");
  BannedDate.parse = banned("Date.parse()");
  BannedDate.UTC = banned("Date.UTC()");
  globalThis.Date = BannedDate;
  Math.random = banned("Math.random()");

  // Capture temporary host bindings into this closure, then strip them from
  // globalThis so scripts only see the documented Workflow surface (#4129).
  const hostTask = __workflow_task;
  const hostLog = __workflow_log;
  const hostPhase = __workflow_phase;
  const hostBudgetTotal = __workflow_budget_total;
  const hostBudgetSpent = __workflow_budget_spent;
  const hostBudgetRemaining = __workflow_budget_remaining;

View on GitHub (pinned to 8880682c63)

Solutions

  1. Derive the id first and bail out early: let id = NativeMemoryStore::workspace_id(cwd)?; if None, fall back to MemoryScope::Global or surface 'workspace memory requires a git origin remote' to the user.
  2. If you are in a repo, add an origin remote: git remote add origin <url>, then retry.
  3. If workspace isolation is not needed, call the same API with MemoryScope::Global, which needs no id.
  4. In tests, pass a fixed id (e.g. Some("repo-a")) like native_memory/tests.rs does.

Example fix

// before
store.remember(MemoryScope::Workspace, None, "note")?;

// after
let id = NativeMemoryStore::workspace_id(&cwd)?
    .context("workspace memory requires a git origin remote")?;
store.remember(MemoryScope::Workspace, Some(&id), "note")?;
Defensive patterns

Strategy: validation

Validate before calling

let workspace_id = NativeMemoryStore::workspace_id(&cwd)?;
let scope = match (&workspace_id, wanted_scope) {
    (Some(_), MemoryScope::Workspace) => MemoryScope::Workspace,
    (None, MemoryScope::Workspace) => MemoryScope::Global, // or bail with your own message
    (_, other) => other,
};
store.remember(scope, workspace_id.as_deref(), note)?;

Type guard

fn workspace_scope_available(cwd: &Path) -> bool {
    Command::new("git").arg("-C").arg(cwd)
        .args(["config", "--get", "remote.origin.url"])
        .output().map(|o| o.status.success() && !o.stdout.trim().is_empty()).unwrap_or(false)
}

Try / catch

match store.remember(MemoryScope::Workspace, workspace_id.as_deref(), note) {
    Ok(_) => {}
    Err(e) if e.to_string().contains("workspace scope requires a workspace id") => {
        // degrade to global scope or tell the user to add an origin remote
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling remember/edit/retire with MemoryScope::Workspace and workspace_id=None; e.g. store.remember(MemoryScope::Workspace, None, "note"). The tool layer (tools/native_memory.rs, tools/remember.rs) hits this when the session's workspace has no derivable git origin, so no id is passed down.

Common situations: Running the TUI in a plain folder that is not a git repository; a freshly created repo that has no `origin` remote yet; a detached worktree cloned without remotes; tests that construct MemoryScope::Workspace directly without first computing an id.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/71bab292858eda19. Report an issue: GitHub.