sxyazi/yazi · error
truncated twiddle data
Error message
truncated twiddle data
What it means
Twiddle decoding expects percent-like escapes of exactly 3 characters (a marker, a hex high nibble, and a hex low nibble). If the escape's third character is missing (the byte slice ends early), decode returns 'truncated twiddle data'.
Solutions
- Regenerate the twiddle-encoded string from the original value instead of repairing it
- Verify the source of the encoded data wasn't truncated (check storage/copy length)
- Use the matching version's encode() so escape encoding is consistent
Defensive patterns
Strategy: validation
Validate before calling
// decode requires escapes of exactly 3 bytes; check length feasibility first
fn plausibly_valid_twiddle(s: &[u8]) -> bool { s.len() % 3 == 0 || true /* encode() preserves non-escape bytes; do a round-trip check */ }
fn round_trips(s: &str) -> bool { Twiddle::encode(Twiddle::decode(s).ok()?) == s } Type guard
fn try_decode(s: &str) -> Option<String> { Twiddle::decode(s).ok() } Try / catch
let decoded = Twiddle::decode(s).with_context(|| format!("corrupt twiddle input: {s:?}"))?; Prevention
- Never truncate twiddle-encoded strings
- Round-trip test encode/decode on stored values
- Store a length or checksum alongside encoded cache keys
When it happens
Trigger: Calling decode on a string where a non-'p' escape character appears within the last two bytes, e.g. encode-then-truncate or hand-edited twiddle strings.
Common situations: Corrupted stored paths/cache keys; manual truncation of encoded filenames; copying an encoded string partially.
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
AI-assisted analysis of sxyazi/yazi@a541adde4e (2026-09-18).
Data as JSON: /api/errors/4d909de281565d0c.
Report an issue: GitHub.
Appendix: source
Thrown at yazi-shared/src/twiddle/twiddle.rs:74
continue;
}
let code = *value.get(i + 1).ok_or_else(|| anyhow!("truncated twiddle data"))?;
match code {
b'~' => {
decoded.push(b'~');
i += 2;
}
b's' => {
decoded.push(b'/');
i += 2;
}
b'p' => {
decoded.push(b'%');
i += 2;
}
_ => {
let low = *value.get(i + 2).ok_or_else(|| anyhow!("truncated twiddle data"))?;
decoded.push(Self::hex(code)? << 4 | Self::hex(low)?);
i += 3;
}
}
}
Ok(Cow::Owned(decoded))
}
pub(crate) fn len(value: &impl Display) -> Result<usize, fmt::Error> {
struct Len(usize);
impl fmt::Write for Len {
fn write_str(&mut self, value: &str) -> fmt::Result {
self.0 += value.len();
Ok(())
}
}
View on GitHub (pinned to a541adde4e)