Skip to main content

max / makenotwork

Bento: name the tracked Cargo.lock, not the dirty tree pter 0.2.1 failed at verify with "1 files in the working directory contain changes that were not yet committed into git: Cargo.lock" (build 325). The cause is two facts cargo's message names neither of: the crate tracks a lock, and the build worktree is under ~/Code, where the [patch] block makes cargo re-resolve and rewrite it. crate_preflight now asks the build host both questions and fails with a message that states them and points at untracking the lock. Both wrong fixes are one flag away -- --allow-dirty publishes a lock nobody reviewed, and committing the rewritten one resolves differently on the next machine -- so the message rules them out by name. Asked on the build host, not the daemon: the worktree over there is the tree that gets published. An Err stays "the question could not be asked" rather than becoming a finding, matching the credentials check beside it. worktree_root is under ~/Code deliberately (Host::worktree_root), so the patch block is not the half to change and the message says so.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-27 20:52 UTC
Signed with PGP, not checked
Commit: 26c55436ac5d71028150c73b4ecdee190d31fc69
Parent: 176a696
2 files changed, +164 insertions, -23 deletions
@@ -3302,36 +3302,32 @@
3302 3302 checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
3303 3303
3304 3304 [[patch.unused]]
3305 - name = "synckit-client"
3306 - version = "0.9.1"
3307 -
3308 - [[patch.unused]]
3309 - name = "synckit-config"
3310 - version = "0.2.0"
3305 + name = "quasi-type"
3306 + version = "0.1.0"
3311 3307
3312 3308 [[patch.unused]]
3313 3309 name = "quasi-axum"
3314 - version = "0.56.0"
3310 + version = "0.70.0"
3315 3311
3316 3312 [[patch.unused]]
3317 3313 name = "quasi-basics"
3318 - version = "0.56.0"
3314 + version = "0.70.0"
3319 3315
3320 3316 [[patch.unused]]
3321 3317 name = "quasi-http"
3322 - version = "0.56.0"
3318 + version = "0.70.0"
3323 3319
3324 3320 [[patch.unused]]
3325 3321 name = "quasi-immediate"
3326 - version = "0.56.0"
3322 + version = "0.70.0"
3327 3323
3328 3324 [[patch.unused]]
3329 3325 name = "quasi-notifs"
3330 - version = "0.56.0"
3326 + version = "0.70.0"
3331 3327
3332 3328 [[patch.unused]]
3333 3329 name = "quasi-router"
3334 - version = "0.56.0"
3330 + version = "0.70.0"
3335 3331
3336 3332 [[patch.unused]]
3337 3333 name = "quasi-store"
@@ -3339,11 +3335,23 @@
3339 3335
3340 3336 [[patch.unused]]
3341 3337 name = "quasi-tauri"
3342 - version = "0.56.0"
3338 + version = "0.70.0"
3343 3339
3344 3340 [[patch.unused]]
3345 3341 name = "quasi-webview"
3346 - version = "0.56.0"
3342 + version = "0.70.0"
3343 +
3344 + [[patch.unused]]
3345 + name = "docengine"
3346 + version = "0.7.0"
3347 +
3348 + [[patch.unused]]
3349 + name = "synckit-client"
3350 + version = "0.9.1"
3351 +
3352 + [[patch.unused]]
3353 + name = "synckit-config"
3354 + version = "0.2.0"
3347 3355
3348 3356 [[patch.unused]]
3349 3357 name = "kberg"
@@ -3356,11 +3364,3 @@
3356 3364 [[patch.unused]]
3357 3365 name = "tagtree"
3358 3366 version = "0.4.1"
3359 -
3360 - [[patch.unused]]
3361 - name = "quasi-type"
3362 - version = "0.1.0"
3363 -
3364 - [[patch.unused]]
3365 - name = "docengine"
3366 - version = "0.7.0"
@@ -1124,6 +1124,52 @@
1124 1124 format!("git -C {repo} fetch --all --tags --prune")
1125 1125 }
1126 1126
1127 + /// Probe for the one failure a tracked `Cargo.lock` hits inside Bento's
1128 + /// worktree. Exits 0 when the crate tracks a lock AND the checkout sits under a
1129 + /// `.cargo/config.toml` declaring `[patch]`; 1 otherwise.
1130 + ///
1131 + /// Both halves are needed and cargo reports neither. Under a `[patch]` block
1132 + /// cargo re-resolves and rewrites the lock's `[[patch.unused]]` entries, so
1133 + /// `cargo publish --dry-run` refuses the tree with "1 files in the working
1134 + /// directory contain changes that were not yet committed into git: Cargo.lock"
1135 + /// -- naming the lock and nothing about why it moved. pter 0.2.1 lost half an
1136 + /// hour to that message on build 325.
1137 + ///
1138 + /// `~/Code/.bento` is under `~/Code` deliberately, so that the patch block
1139 + /// reaches the build (see [`crate::topology::Host::worktree_root`]). The patch
1140 + /// block is therefore not the half to remove, which is why this is worth
1141 + /// saying rather than leaving cargo to be cryptic about it.
1142 + ///
1143 + /// `repo` is interpolated unquoted for the `~`, matching [`git_fetch_cmd`];
1144 + /// `pwd -P` then hands the loop an absolute path to walk up from.
1145 + pub fn tracked_lock_under_patch_cmd(repo: &str) -> String {
1146 + format!(
1147 + "git -C {repo} ls-files --error-unmatch Cargo.lock >/dev/null 2>&1 || exit 1; \
1148 + d=$(cd {repo} && pwd -P) || exit 1; \
1149 + while [ -n \"$d\" ] && [ \"$d\" != / ]; do \
1150 + for c in \"$d/.cargo/config.toml\" \"$d/.cargo/config\"; do \
1151 + [ -f \"$c\" ] && grep -q '^\\[patch' \"$c\" && exit 0; \
1152 + done; d=$(dirname \"$d\"); done; exit 1"
1153 + )
1154 + }
1155 +
1156 + /// What to say when [`tracked_lock_under_patch_cmd`] answers yes. Names both
1157 + /// facts, because the error cargo would otherwise print names neither, and
1158 + /// closes off the two wrong fixes that are both one flag away.
1159 + pub fn tracked_lock_under_patch_problem(repo: &str) -> String {
1160 + format!(
1161 + "`Cargo.lock` is tracked and {repo} sits under a `.cargo/config.toml` \
1162 + declaring `[patch]`. Cargo re-resolves there and rewrites the lock, so \
1163 + `cargo publish --dry-run` will refuse the tree as dirty and name only \
1164 + the lock. Untrack it: `git rm --cached Cargo.lock`. Every other library \
1165 + in this tree already does. Not `--allow-dirty`, which publishes a lock \
1166 + nobody reviewed, and not committing the rewritten lock, which resolves \
1167 + differently on the next machine and fails there instead. The build \
1168 + worktree is under `~/Code` on purpose so the patch block applies to it; \
1169 + that is not the half to change."
1170 + )
1171 + }
1172 +
1127 1173 /// Does `tag` resolve to a commit in this checkout? Run only when the checkout
1128 1174 /// has already failed, to say WHY: an absent tag is an untagged or unpushed
1129 1175 /// release, while a tag that resolves fine means the checkout was refused for a
@@ -1524,8 +1570,31 @@
1524 1570 })?
1525 1571 .0 == 0;
1526 1572
1573 + // Asked of the build host rather than the daemon: the tree
1574 + // that gets published is the worktree over there, and it is the
1575 + // one whose `[patch]` ancestry decides this. An Err is "the
1576 + // question could not be asked" and is not a finding -- same
1577 + // rule as the credentials check above, for the same reason it
1578 + // was written that way.
1579 + let build_repo = ctx.repo_for(&ctx.build_host).to_string();
1580 + let patched_lock =
1581 + ctx.run(
1582 + &ctx.build_host.clone(),
1583 + &tracked_lock_under_patch_cmd(&build_repo),
1584 + )
1585 + .map_err(|e| {
1586 + format!(
1587 + "could not check for a tracked Cargo.lock on `{}`: {e}",
1588 + ctx.build_host
1589 + )
1590 + })?
1591 + .0 == 0;
1592 +
1527 1593 let published = RecipeCtx::published_versions(&meta.name);
1528 - let problems = crate_publish_problems(&meta, clonable, &published, creds);
1594 + let mut problems = crate_publish_problems(&meta, clonable, &published, creds);
1595 + if patched_lock {
1596 + problems.push(tracked_lock_under_patch_problem(&build_repo));
1597 + }
1529 1598 if !problems.is_empty() {
1530 1599 return Err(format!(
1531 1600 "{} {} is not safe to publish:\n - {}",
@@ -3601,4 +3670,76 @@
3601 3670 let silent = worktree_failure_reason("pom-v0.4.5", true, " ");
3602 3671 assert!(silent.contains("said nothing"), "{silent}");
3603 3672 }
3673 +
3674 + /// Run the probe for real rather than asserting on its text: it is shell,
3675 + /// and the thing worth knowing is whether `sh` agrees, not whether the
3676 + /// string looks right.
3677 + fn probe(repo: &std::path::Path) -> bool {
3678 + std::process::Command::new("sh")
3679 + .arg("-c")
3680 + .arg(tracked_lock_under_patch_cmd(&repo.display().to_string()))
3681 + .status()
3682 + .unwrap()
3683 + .success()
3684 + }
3685 +
3686 + /// A crate under a `[patch]` root, with and without its lock tracked.
3687 + ///
3688 + /// pter 0.2.1 is the case: the tracked half was true, the patch half was
3689 + /// true, and cargo reported only "Cargo.lock" (build 325). Untracking the
3690 + /// lock in `a9969a9` is what fixed it, and this asserts the probe agrees
3691 + /// with that fix in both directions.
3692 + #[test]
3693 + fn a_tracked_lock_is_only_a_problem_under_a_patch_block() {
3694 + let root = tempfile::tempdir().unwrap();
3695 + let repo = root.path().join("crate");
3696 + std::fs::create_dir_all(&repo).unwrap();
3697 + let git = |args: &[&str]| {
3698 + std::process::Command::new("git")
3699 + .args(args)
3700 + .current_dir(&repo)
3701 + .env("GIT_AUTHOR_NAME", "t")
3702 + .env("GIT_AUTHOR_EMAIL", "t@t")
3703 + .env("GIT_COMMITTER_NAME", "t")
3704 + .env("GIT_COMMITTER_EMAIL", "t@t")
3705 + .output()
3706 + .unwrap()
3707 + };
3708 + git(&["init", "-q", "."]);
3709 + std::fs::write(repo.join("Cargo.lock"), "# lock\n").unwrap();
3710 +
3711 + // Lock present but untracked, no patch anywhere: nothing to say.
3712 + assert!(!probe(&repo));
3713 +
3714 + // Tracked, still no patch root. Every library that commits a lock and
3715 + // builds outside `~/Code` lives here, and it publishes fine.
3716 + git(&["add", "Cargo.lock"]);
3717 + git(&["commit", "-qm", "lock"]);
3718 + assert!(!probe(&repo));
3719 +
3720 + // The ancestor declares `[patch]`. Both halves now hold.
3721 + std::fs::create_dir_all(root.path().join(".cargo")).unwrap();
3722 + std::fs::write(
3723 + root.path().join(".cargo/config.toml"),
3724 + "[patch.\"https://makenot.work/git/max/docengine.git\"]\ndocengine = { path = \"x\" }\n",
3725 + )
3726 + .unwrap();
3727 + assert!(probe(&repo));
3728 +
3729 + // Untracking the lock is the fix, and the probe has to agree that it is.
3730 + git(&["rm", "-q", "--cached", "Cargo.lock"]);
3731 + assert!(!probe(&repo));
3732 + }
3733 +
3734 + /// The message exists because cargo's names neither fact and both wrong
3735 + /// fixes are one flag away. Assert it still says all four things.
3736 + #[test]
3737 + fn the_tracked_lock_message_names_both_facts_and_refuses_both_wrong_fixes() {
3738 + let msg = tracked_lock_under_patch_problem("~/Code/.bento/pter/pter");
3739 + assert!(msg.contains("Cargo.lock"), "{msg}");
3740 + assert!(msg.contains("[patch]"), "{msg}");
3741 + assert!(msg.contains("git rm --cached"), "{msg}");
3742 + assert!(msg.contains("--allow-dirty"), "{msg}");
3743 + assert!(msg.contains("~/Code/.bento/pter/pter"), "{msg}");
3744 + }
3604 3745 }