Hmbown/CodeWhale · error

workspace identity must be a lowercase SHA-256 hash

Error message

workspace identity must be a lowercase SHA-256 hash

What it means

safe_component validates that a value used as a filesystem path component in the memory store is exactly 64 lowercase hex characters, i.e. a lowercase SHA-256 hash. Workspace identities are hashed before being embedded in directory names, so anything else is rejected to prevent path injection and ambiguous layout.

Solutions

  1. Hash the workspace identity with SHA-256 and lowercase-hex-encode it before passing it in.
  2. If you already have a hash, lowercase it and verify it is exactly 64 hex characters.
  3. Use the library's own workspace-scope derivation helper rather than constructing the identifier manually.

Example fix

// before
let scope = workspace_scope("/home/me/project")?; // rejected
// after
let digest = Sha256::digest("/home/me/project");
let scope = workspace_scope(hex::encode(digest))?; // 64 lowercase hex chars
Defensive patterns

Strategy: type-guard

Validate before calling

fn is_sha256_lower_hex(s: &str) -> bool {
    s.len() == 64 && s.bytes().all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
}

Type guard

fn valid_workspace_id(id: &str) -> Option<&str> {
    (id.len() == 64 && id.bytes().all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())).then_some(id)
}

Prevention

When it happens

Trigger: Calling workspace_scope (or anything else routing through safe_component) with a raw workspace name/path instead of its 64-char lowercase hex SHA-256, or with an uppercase-hex hash.

Common situations: Passing a project directory path or workspace name directly instead of hashing it; using an uppercase digest from a different hashing tool; truncating or padding the hash.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/35000472afa9d053. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/native_memory.rs:620

                match store.get(&access, &m.id) {
                    Ok(current) => {
                        store.forget(&access, &current.id, current.revision)?;
                    }
                    Err(codewhale_memory::Error::NotFound) => {}
                    Err(e) => return Err(e.into()),
                }
            }
        }
        Ok(())
    }
}
fn safe_component(value: &str) -> Result<()> {
    if value.len() != 64
        || !value
            .bytes()
            .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
    {
        bail!("workspace identity must be a lowercase SHA-256 hash");
    }
    Ok(())
}

/// Compose the user-memory prompt block for the native store resolved from a
/// memory path. Single seam used by the engine, the TUI system-prompt
/// builder, and the context report so all three describe the same bytes.
/// Returns `None` when memory is disabled, the path is not a native
/// `memory/global/MEMORY.md` layout, or there is nothing worth injecting.
/// The block is a `codewhale.memory.context.v1` envelope: memory entries are
/// untrusted evidence, never instructions.
#[must_use]
pub fn native_prompt_block(enabled: bool, memory_path: &Path, workspace: &Path) -> Option<String> {
    if !enabled {
        return None;
    }
    NativeMemoryStore::from_global_path(memory_path)?
        .prompt_block(workspace, 32, 12_000)

View on GitHub (pinned to 73e0f67d83)