jdx/mise · error

must not record a pin before replacement succeeds

Error message

must not record a pin before replacement succeeds

What it means

This panic comes from a unit test (failed_replacement_restores_previous_plugin) for packslip::replace, not from production code. The closure passed to replace() records the installed-version pin; the test passes a closure that panics to prove the invariant that the pin callback is never invoked when the payload replacement itself fails (here because the payload directory 'missing' does not exist). If you see this panic while running tests, the replace() implementation started recording a pin before the plugin directory swap had succeeded, which would leave a stale pin after a failed install.

Source

Thrown at src/plugins/packslip.rs:345

        assert!(result.is_err());
        assert_eq!(
            std::fs::read_to_string(plugin.join("metadata.lua")).unwrap(),
            "previous"
        );
    }

    #[test]
    fn failed_replacement_restores_previous_plugin() {
        let temp = tempfile::tempdir().unwrap();
        let plugin = temp.path().join("plugin");
        std::fs::create_dir(&plugin).unwrap();
        std::fs::write(plugin.join("metadata.lua"), "previous").unwrap();
        assert!(
            replace(
                &temp.path().join("missing"),
                &plugin,
                &temp.path().join("backup"),
                || panic!("must not record a pin before replacement succeeds")
            )
            .is_err()
        );
        assert_eq!(
            std::fs::read_to_string(plugin.join("metadata.lua")).unwrap(),
            "previous"
        );
    }
}

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Keep the pin-recording closure invoked only after the payload replacement has fully succeeded and the old plugin has been moved to backup
  2. Ensure every early-return/`?` failure path in replace() (missing payload dir, copy error, backup error) bypasses the callback entirely
  3. Run `mise run test:unit` (or `cargo test --bin mise packslip`) to confirm the test passes after the refactor

Example fix

// before (regression in replace())
let pin_result = record_pin();
fs::rename(&payload, &plugin)?;
// after
fs::rename(&payload, &plugin)?;
let pin_result = record_pin();
Defensive patterns

Strategy: try-catch

Try / catch

// Rust: the panic is a test assertion — no catch needed. If embedding replace(), assert the Err path:
let result = replace(&payload, &plugin, &backup, || record_pin());
assert!(result.is_err());
// and verify the previous plugin content is untouched.

Prevention

When it happens

Trigger: Running `cargo test failed_replacement_restores_previous_plugin` (or the packslip test module) against a modified replace() in src/plugins/packslip.rs that calls the pin-recording callback before the payload copy/rename completes, or calls it even when the payload path is missing and the operation returns Err.

Common situations: Refactoring packslip plugin replacement logic (reordering the backup/swap/pin steps, moving the pin write earlier, or changing error propagation so the callback runs on the failure path); a regression during work on the packslip backend's install/upgrade flow.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/29803edcc41ccf40. Report an issue: GitHub.