FuelLabs/sway · error
invalid salt in lock file: {e}
Error message
invalid salt in lock file: {e} What it means
In parse_pkg_dep_line, the parenthesized trailing component of a dependency line is parsed as a contract-dependency salt with fuel_tx::Salt::from_str, which requires exactly 64 hex characters (a 32-byte salt). Any non-hex or wrong-length body fails and is wrapped with this message. Note the parser blindly drops the last character of the token expecting ')', so even a technically-valid salt can fail if the closing paren layout is wrong; only contract-dependency lines carry salts.
Source
Thrown at forc-pkg/src/lock.rs:332
.next()
.ok_or_else(|| anyhow!("missing closing parenthesis"))?;
// The rest is the unique package string and possibly the salt.
let s = &s[dep_name.len() + ")".len()..];
(Some(dep_name), s)
}
};
// Check for salt.
let mut iter = s.split('(');
let pkg_str = iter
.next()
.ok_or_else(|| anyhow!("missing pkg string"))?
.trim();
let salt_str = iter.next().map(|s| s.trim()).map(|s| &s[..s.len() - 1]);
let salt = match salt_str {
Some(salt_str) => Some(
fuel_tx::Salt::from_str(salt_str)
.map_err(|e| anyhow!("invalid salt in lock file: {e}"))?,
),
None => None,
};
Ok((dep_name, pkg_str, salt))
}
pub fn print_diff(member_names: &HashSet<String>, diff: &Diff) {
print_removed_pkgs(member_names, diff.removed.iter().copied());
print_added_pkgs(member_names, diff.added.iter().copied());
}
pub fn print_removed_pkgs<'a, I>(member_names: &HashSet<String>, removed: I)
where
I: IntoIterator<Item = &'a PkgLock>,
{
for pkg in removed {
if !member_names.contains(&pkg.name) {View on GitHub (pinned to 47e5e902fa)
Solutions
- Write the salt as exactly 64 hex characters inside the trailing parentheses, e.g. (auth git+...#c0ffee) 0000000000000000000000000000000000000000000000000000000000000000.
- Delete Forc.lock and rebuild to regenerate contract-dependency lines from Forc.toml's [contract-dependencies] section.
- If maintaining manually, verify length with: echo -n "$SALT" | wc -c (must be 64).
Example fix
# before (Forc.lock) contract-dependencies = [ "(auth git+https://github.com/x/auth#abc) 0xdeadbeef" ] # after contract-dependencies = [ "(auth git+https://github.com/x/auth#abc) deadbeef00000000000000000000000000000000000000000000000000000000" ]
Defensive patterns
Strategy: validation
Validate before calling
// Rust, pre-check the salt body of a contract-dependency line:
fn valid_salt_body(line: &str) -> Option<bool> {
let after = line.split('(').nth(1)?;
let body = after.strip_suffix(')').unwrap_or(after).trim();
Some(body.len() == 64 && body.chars().all(|c| c.is_ascii_hexdigit()))
} Try / catch
// anyhow Result at lock load; map to a regeneration flow:
match Lock::from_path(&p).and_then(|l| l.to_graph()) {
Err(e) if e.to_string().contains("invalid salt") => { /* rm Forc.lock; rebuild */ }
other => other.unwrap(),
} Prevention
- Author contract-dependency salts as exactly 64 hex chars (no 0x) in lock lines.
- Declare salts in Forc.toml as HexSalt (0x + 64 hex) and let forc write the lock.
- Lint salt length when generating lock files programmatically.
When it happens
Trigger: A contract-dependency entry in Forc.lock whose salt is not 64 hex chars - truncated during editing, 0x-prefixed when the parser does not expect a prefix, or with a missing ')' shifting the split.
Common situations: Hand-copied salts of the wrong length; prefixes from other tools; merge damage limited to the salt segment.
Related errors
- failed to parse dependency "{}": {}
- missing closing parenthesis
- missing pkg string
- failed to read {}: {}
- invalid 'source' entry for package {} lock: {:?}
AI-assisted analysis of FuelLabs/sway@47e5e902fa (2026-08-16).
Data as JSON: /api/errors/926ea35bac3d0a1c.
Report an issue: GitHub.