rtk-ai/rtk · error
Invalid hash format in
Error message
Invalid hash format in {} (expected 'hash filename') What it means
read_stored_hash parses a sha256sum-format checksum file: it expects a single line of '<hash> <filename>' separated by exactly two spaces. If splitting the first line on the two-space separator does not yield exactly 2 parts, the file's format is not what rtk wrote, and it bails so verification fails safely rather than comparing garbage.
Solutions
- Regenerate the hash file the way rtk expects: `cd <hook dir> && sha256sum rtk-rewrite.sh > rtk-rewrite.sh.sha256` (sha256sum emits the two-space separator)
- Inspect the file: `cat -A <hook>.sha256` to see hidden spacing; fix it to '<64-hex> <filename>' with exactly two spaces and a single line
- Reinstall the hook via rtk (e.g. `rtk init -g`) so rtk rewrites the checksum file itself
Example fix
// before (hand-written, single space) abc123 rtk-rewrite.sh // after printf '%s %s\n' "$(sha256sum rtk-rewrite.sh | cut -d' ' -f1)" "rtk-rewrite.sh" > rtk-rewrite.sh.sha256
Defensive patterns
Strategy: validation
Validate before calling
// validate a sha256sum file before running verify
awk 'NR==1 && split($0, a, " ") == 2 { exit (length(a[1])==64 && a[1] ~ /^[0-9a-fA-F]+$/) ? 0 : 1 }' "$hook.sha256" || regen_checksums Type guard
fn is_sha256sum_line(line: &str) -> bool {
match line.splitn(2, " ").collect::<Vec<_>>()[..] {
[h, _f] => h.len() == 64 && h.chars().all(|c| c.is_ascii_hexdigit()),
_ => false,
}
} Try / catch
match verify_hook_at(&path) {
Err(e) if e.to_string().contains("Invalid hash format") => regenerate_checksum_file(&path)?,
other => other?,
} Prevention
- Only generate checksums with `sha256sum` (two-space separator), not `md5sum` or `shasum` defaults
- Do not hand-edit .sha256 files; let `rtk init` write them
- Audit with `cat -A` after editors touch hook directories (trailing-space stripping breaks the format)
When it happens
Trigger: Calling verify_hook_at on a hook whose .sha256 file was hand-edited, truncated, generated with a single-space separator (`sha256sum file > f` piped differently), or written by another tool with a different line layout; also when the file contains extra lines/whitespace such that splitn(2, " ") yields != 2 parts.
Common situations: Manually regenerating checksums with `shasum -a 256` variants that pad differently; editors stripping trailing spaces or normalizing double spaces; partial writes/corruption of the hash file in ~/.claude/hooks.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Invalid SHA-256 hash in
- Failed to uninstall RTK from one or more Droid hook files
- hook stdin exceeds byte limit
- is not a JSON file (detected ). Use `rtk read` for non-JSON…
- just-created json object
AI-assisted analysis of rtk-ai/rtk@0924356b4c (2026-09-19).
Data as JSON: /api/errors/98f80228eb82baeb.
Report an issue: GitHub.
Appendix: source
Thrown at src/hooks/integrity.rs:182
}
/// Read the stored hash from the hash file.
///
/// Expects exact `sha256sum -c` format: `<64 hex> <filename>\n`
/// Rejects malformed files rather than silently accepting them.
fn read_stored_hash(path: &Path) -> Result<String> {
let content = fs::read_to_string(path)
.with_context(|| format!("Failed to read hash file: {}", path.display()))?;
let line = content
.lines()
.next()
.with_context(|| format!("Empty hash file: {}", path.display()))?;
// sha256sum format uses two-space separator: "<hash> <filename>"
let parts: Vec<&str> = line.splitn(2, " ").collect();
if parts.len() != 2 {
anyhow::bail!(
"Invalid hash format in {} (expected 'hash filename')",
path.display()
);
}
let hash = parts[0];
if hash.len() != 64 || !hash.chars().all(|c| c.is_ascii_hexdigit()) {
anyhow::bail!("Invalid SHA-256 hash in {}", path.display());
}
Ok(hash.to_string())
}
/// Resolve the default hook path (~/.claude/hooks/rtk-rewrite.sh)
pub fn resolve_hook_path() -> Result<PathBuf> {
resolve_claude_dir().map(|dir| dir.join(HOOKS_SUBDIR).join(REWRITE_HOOK_FILE))
}
View on GitHub (pinned to 0924356b4c)