Skip to content

fix(lockfile): prune orphan tool entries on mise lock#8265

Merged
jdx merged 2 commits intojdx:mainfrom
mackwic:codex/lockfile-prune-orphans
Feb 24, 2026
Merged

fix(lockfile): prune orphan tool entries on mise lock#8265
jdx merged 2 commits intojdx:mainfrom
mackwic:codex/lockfile-prune-orphans

Conversation

@mackwic
Copy link
Contributor

@mackwic mackwic commented Feb 20, 2026

Summary

When tools are removed from config, mise lock could keep stale entries in mise.lock.
This PR makes full lock runs prune orphan tool entries so lockfiles stay aligned with current config.

Follow-up fixes were added after initial review to cover subtle lockfile compatibility cases.

Changes

  • Prune stale lockfile tool entries during full mise lock runs (no tool filter).
  • Keep existing behavior for filtered runs (mise lock <tool>), which do not prune other tools.
  • Handle the “all tools removed” case:
    • even when the resolved toolset is empty, full mise lock now prunes/removes stale lockfile entries.
  • Preserve compatibility with legacy lockfile keying:
    • pruning now keeps entries by either configured tool short name or backend identifier
    • this avoids dropping legacy entries like [[tools.jq]] with backend = "aqua:jqlang/jq".
  • Add Lockfile::retain_tools_by_short_or_backend(...) and run conda package cleanup after prune.
  • Add/extend regression e2e coverage:
    • e2e/lockfile/test_lockfile_prune_removed_tool
      • tool replacement prune
      • remove-all-tools prune
  • Keep e2e/cli/test_lock_platform_merge passing with legacy lockfile shape.

Why

Prevents orphan entries in mise.lock, keeps lockfiles deterministic/config-aligned, and avoids regressions with existing lockfile formats.

Validation

  • cargo fmt --all --check
  • cargo clippy -- -D warnings
  • cargo clippy --all-features --all-targets -- -D warnings
  • cargo test -q test_retain_tools_by_short_prunes_removed_tools
  • cargo test -q test_retain_tools_by_short_cleans_unreferenced_conda_packages
  • cargo test -q test_retain_tools_by_short_or_backend_preserves_legacy_keyed_entries
  • cargo test -q test_should_prune_stale_lockfile_entries_without_tool_filter
  • cargo test -q test_should_not_prune_stale_lockfile_entries_with_tool_filter
  • cargo test -q test_prune_stale_entries_with_empty_tools_prunes_all_entries
  • cargo test -q test_prune_stale_entries_with_filter_keeps_existing_entries
  • cargo test -q test_prune_stale_entries_preserves_legacy_keyed_backend_match
  • ./e2e/run_test e2e/lockfile/test_lockfile_prune_removed_tool
  • ./e2e/run_test e2e/cli/test_lock_platform_merge

Copilot AI review requested due to automatic review settings February 20, 2026 14:57
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @mackwic, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves an issue where mise lock could retain stale tool entries in the lockfile after a tool was replaced in the configuration. The changes ensure that the mise.lock file is always aligned with the project's current tool configuration by automatically removing unreferenced entries during a full lock operation, thereby enhancing lockfile determinism and accuracy.

Highlights

  • Lockfile Pruning: Implemented pruning of orphan tool entries in mise.lock during full mise lock runs, ensuring the lockfile accurately reflects the current configuration.
  • Filtered Lock Runs: Maintained existing behavior for filtered mise lock <tool> runs, which will not prune other tools from the lockfile.
  • New Lockfile Method: Introduced Lockfile::retain_tools_by_short(...) to filter tools and perform subsequent cleanup of unreferenced conda packages.
  • Testing: Added a new end-to-end regression test and unit tests for the prune behavior in Lockfile and the prune decision logic in cli lock.
Changelog
  • e2e/lockfile/test_lockfile_prune_removed_tool
    • Added a new end-to-end test to verify that removed tools are correctly pruned from the lockfile when a configured tool is replaced and mise lock is executed.
  • src/cli/lock.rs
    • Implemented logic within the Lock command to prune stale lockfile entries when no specific tool filter is provided.
    • Introduced a should_prune_stale_lockfile_entries helper method to control when pruning is performed.
    • Added unit tests for the should_prune_stale_lockfile_entries method, covering scenarios with and without tool filters.
  • src/lockfile.rs
    • Added a new public method retain_tools_by_short to the Lockfile struct, allowing tools to be filtered based on a set of short names and triggering cleanup of unreferenced conda packages.
    • Introduced helper functions (basic_tool, tool_with_conda_dep, add_test_conda_package) to facilitate testing of lockfile modifications.
    • Added unit tests for retain_tools_by_short to confirm that removed tools are pruned and associated unreferenced conda packages are cleaned up.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a mechanism to prune orphan tool entries from the lockfile during full mise lock runs. This ensures that mise.lock remains synchronized with the current configuration when tools are removed. The implementation includes a new retain_tools_by_short method in the Lockfile struct, logic in the lock command to trigger pruning when no tool filter is applied, and comprehensive tests.

One logic issue was identified: the pruning process is currently skipped if the resulting toolset is entirely empty (e.g., when all tools are removed from the configuration). This prevents the lockfile from being cleaned up or removed in such cases.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds functionality to automatically prune orphaned tool entries from lockfiles during full mise lock runs, ensuring lockfiles stay synchronized with the current configuration.

Changes:

  • Added Lockfile::retain_tools_by_short() method to prune tool entries not present in the current configuration
  • Modified mise lock command to call the pruning logic only when no tool filter is specified
  • Added comprehensive unit and e2e tests for the pruning behavior

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/lockfile.rs Added retain_tools_by_short() method with conda package cleanup, plus test helpers and unit tests
src/cli/lock.rs Integrated pruning logic with should_prune_stale_lockfile_entries() check, added unit tests
e2e/lockfile/test_lockfile_prune_removed_tool Added regression test verifying stale tool entries are removed during full lock

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mackwic mackwic force-pushed the codex/lockfile-prune-orphans branch 3 times, most recently from 66e63c9 to 764cf97 Compare February 20, 2026 15:27
Copy link
Owner

@jdx jdx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was AI-generated.

Overall the core logic is sound — pruning stale lockfile entries on full mise lock runs is the right approach, and the decision to skip pruning on filtered runs (mise lock <tool>) is sensible.

A few observations:

1. Redundant should_prune_stale_lockfile_entries() check

should_prune_stale_lockfile_entries() is called both at the call site in the tools.is_empty() branch (line 85) and inside prune_stale_entries_if_needed. The guard is duplicated — the method already short-circuits if self.tool is not empty.

2. configured_backends includes shorts redundantly

In prune_stale_entries_if_needed:

let configured_backends: BTreeSet<String> = tools
    .iter()
    .flat_map(|(ba, _)| [ba.short.clone(), ba.full()])
    .collect();

configured_tools already contains all the shorts, and retain_tools_by_short_or_backend checks keep_shorts first. Having shorts in configured_backends is redundant — not a bug, just unnecessary.

3. Dry-run doesn't show pruning info

When --dry-run is used, the code skips past the prune call. Users won't see that stale entries would be pruned. Similarly in the tools.is_empty() + dry-run path, there's no message about what would happen.

4. The tools.is_empty() prune path is indirectly "remove everything"

When tools.is_empty() and pruning triggers, prune_stale_entries_if_needed builds empty configured_tools/configured_backends sets, so retain_tools_by_short_or_backend removes all entries. This is the intended behavior but reads indirectly — a comment noting this is "prune all since no tools are configured" would help readability.

What looks good:

  • retain_tools_by_short_or_backend checking both short name and backend to preserve legacy lockfile entries is well-designed
  • Conda package cleanup after pruning is a nice touch
  • Test coverage is thorough — unit tests for each scenario plus e2e covering replacement and full removal

@mackwic mackwic force-pushed the codex/lockfile-prune-orphans branch from 764cf97 to 763e2a8 Compare February 22, 2026 10:57
@jdx
Copy link
Owner

jdx commented Feb 22, 2026

AI Generated

Bug: silent lockfile deletion with misleading message

When all tools are removed and mise lock is run:

  1. tools.is_empty() → pruning runs, lockfile deleted
  2. any_tools is never set to true
  3. "No tools configured to lock" prints

The deletion happens silently and the message is misleading. Fix:

let pruned_tools = self.prune_stale_entries_if_needed(&mut lockfile, &tools);
if !pruned_tools.is_empty() {
    lockfile.write(&lockfile_path)?;
    any_tools = true;
    // + some user-visible message
}

Minor: configured_backends includes shorts redundantly

let configured_backends: BTreeSet<String> = tools
    .iter()
    .flat_map(|(ba, _)| [ba.short.clone(), ba.full()])  // ba.short is redundant
    .collect();

configured_tools already holds all shorts and retain_tools_by_short_or_backend checks keep_shorts first, so ba.short in configured_backends never has any effect.

Minor: duplicated logic between stale_entries_if_pruned and prune_stale_entries_if_needed

Both methods independently call configured_tool_selectors + stale_tool_shorts. prune_stale_entries_if_needed could call stale_entries_if_pruned internally to avoid the duplication.

@mackwic mackwic force-pushed the codex/lockfile-prune-orphans branch 2 times, most recently from ad196c2 to 8a44249 Compare February 23, 2026 08:54
@mackwic
Copy link
Contributor Author

mackwic commented Feb 23, 2026

Thanks ! Addressed in 8a44249cf.

Implemented all points:

  1. Silent deletion + misleading message

    • Fixed by tracking did_lockfile_change (instead of any_tools).
    • In the tools.is_empty() prune path, if stale entries are pruned we now:
      • write/remove the lockfile,
      • print a user-visible message (Pruned ... stale tool entry ...),
      • mark the run as changed so we no longer print No tools configured to lock.
  2. configured_backends redundancy

    • Removed short names from configured_backends; it now only contains ba.full().
  3. Duplicate stale/prune logic

    • prune_stale_entries_if_needed now reuses stale_entries_if_pruned for stale-set computation.

Also did a small readability cleanup:

  • should_prune_stale_lockfile_entries -> is_unfiltered_lock_run
  • any_tools -> did_lockfile_change
  • merged prune output helpers into one (show_stale_prune_message)
  • helper path args now use &Path instead of &PathBuf.

Tests updated:

  • renamed e2e to e2e/lockfile/test_lockfile_prunes_orphaned_tools_and_removes_empty_lockfile
  • this e2e now asserts:
    • dry-run prune preview,
    • actual prune message,
    • no misleading No tools configured to lock.
  • added an e2e check in e2e/cli/test_lock to ensure filtered --dry-run does not show prune preview.

@mackwic mackwic force-pushed the codex/lockfile-prune-orphans branch 2 times, most recently from e82fa65 to 65d39c2 Compare February 23, 2026 14:07
Copy link
Owner

@jdx jdx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review (AI-generated)

Good feature — pruning stale lockfile entries when tools are removed from config is a sensible improvement. The approach of only pruning on unfiltered mise lock runs (not mise lock <tool>) is correct. Test coverage is thorough. A few issues worth addressing:

Issues

1. Duplicated prune logic — prune runs twice on non-empty tool sets

When tools is not empty, the code calls prune_stale_entries_if_needed after show_dry_run/before process_tools, but the prune also runs in the tools.is_empty() branch. For a normal mise lock with tools, the lockfile is read once for pruning, then read again for process_tools. The prune result from the first read is discarded because process_tools reads the lockfile fresh:

// dry_run path: prune happens on a NEW lockfile read
let lockfile = Lockfile::read(&lockfile_path)?;
let stale_tools = self.stale_entries_if_pruned(&lockfile, &tools);

// non-dry-run path: reads lockfile AGAIN
let mut lockfile = Lockfile::read(&lockfile_path)?;
self.prune_stale_entries_if_needed(&mut lockfile, &tools);

The non-dry-run path does prune correctly since it mutates the same lockfile that gets written, but this reads the lockfile from disk twice unnecessarily. Consider consolidating to a single Lockfile::read call per pass.

2. did_lockfile_change is always true when tools exist, even if nothing changed

if tools.is_empty() {
    // ...
    continue;
}
did_lockfile_change = true;  // Always true, even if dry_run or nothing actually changed

Previously any_tools had the same behavior so this isn't a regression, but the rename to did_lockfile_change implies semantic accuracy that isn't there. In dry-run mode the lockfile doesn't actually change. Consider keeping the original name any_tools or making the semantics accurate.

3. configured_tool_selectors is called twice per prune check

prune_stale_entries_if_needed calls stale_entries_if_pruned (which calls configured_tool_selectors), then calls configured_tool_selectors again:

fn prune_stale_entries_if_needed(&self, lockfile: &mut Lockfile, tools: &[...]) -> BTreeSet<String> {
    let stale_tools = self.stale_entries_if_pruned(lockfile, tools);  // calls configured_tool_selectors
    if !stale_tools.is_empty() {
        let (configured_tools, configured_backends) = self.configured_tool_selectors(tools);  // again
        lockfile.retain_tools_by_short_or_backend(&configured_tools, &configured_backends);
    }
    stale_tools
}

Could compute the selectors once and pass them to both.

4. retain_tools_by_short_or_backend and stale_tool_shorts duplicate the same filter predicate

These two methods in lockfile.rs have near-identical keep/discard logic. Consider extracting a shared helper, or have retain call stale_tool_shorts to determine what to remove.

5. E2E test file naming

The new test file test_lockfile_prunes_orphaned_tools_and_removes_empty_lockfile is quite long. The PR body references test_lockfile_prune_removed_tool (a different name). Existing tests use shorter names like test_lock_platform_merge. Consider something shorter like test_lockfile_prune_stale.

Looks Good

  • Legacy lockfile compatibility (matching by backend identifier, not just short name) is well thought out
  • Conda package cleanup cascading from tool removal is correct
  • Unit test coverage is comprehensive — short-name matching, backend-identifier matching, conda cleanup, empty toolset, filtered runs
  • Dry-run preview message is a nice touch

This review was generated with the assistance of AI.

@mackwic mackwic force-pushed the codex/lockfile-prune-orphans branch from f4dc63c to 8891451 Compare February 24, 2026 09:28
@mackwic
Copy link
Contributor Author

mackwic commented Feb 24, 2026

Thanks for the review. I addressed points 2–5:

  • (2) Reworked the run-state naming/semantics in lock.rs:
    did_lockfile_change is now has_lock_targets, which better matches its purpose (only suppressing the "No tools configured to lock" message when there is actual lock/prune work).
  • (3) Removed duplicate selector computation in prune flow:
    prune_stale_entries_if_needed now computes selectors once and reuses them.
  • (4) Deduplicated the keep/discard predicate in lockfile.rs:
    extracted should_keep_tool(...) and reused it in both
    retain_tools_by_short_or_backend and stale_tool_shorts.
  • (5) Renamed the e2e test file to a shorter explicit name:
    test_lockfile_prune_stale_tools.

For point (1), I did not change behavior because the current control flow does not prune twice in a single run. In run() (src/cli/lock.rs), execution goes through exactly one of these paths:

if tools.is_empty() {
    // Path A
    ...
    continue;
}

if self.dry_run {
    // Path B
    ...
    continue;
}

// Path C (non-dry-run)
let mut lockfile = Lockfile::read(&lockfile_path)?;
self.prune_stale_entries_if_needed(&mut lockfile, &tools);
self.process_tools(..., &mut lockfile).await?;
lockfile.write(&lockfile_path)?;

References: src/cli/lock.rs:68, src/cli/lock.rs:115, src/cli/lock.rs:126.

So Path A/B/C are mutually exclusive. We do not do "prune twice then discard" in one run. There is still a separate optimization opportunity (not a prune correctness issue): avoiding an extra lockfile read across platform detection and the mutable update path.

I also ran targeted formatting/tests locally for the touched lock/prune paths.

@mackwic
Copy link
Contributor Author

mackwic commented Feb 24, 2026

Hmmm there's no explicit mention about that.. Do you prefer I disclose the amount of AI-generated text in my work ?

@jdx
Copy link
Owner

jdx commented Feb 24, 2026

Thanks for the thorough responses and the updates — the changes look solid. The reworked naming (has_lock_targets), deduplicated should_keep_tool predicate, and the clarification on the mutually exclusive A/B/C paths all make sense. I agree it's not a double-prune issue, just the extra lockfile read optimization opportunity you noted.

Re: AI disclosure — no, you don't need to disclose that. There's no requirement for it on this project.


Note: this review was AI-generated using Claude Code.

@jdx jdx merged commit e111af9 into jdx:main Feb 24, 2026
33 checks passed
mise-en-dev added a commit that referenced this pull request Feb 25, 2026
### 🚀 Features

- **(conda)** replace custom backend with rattler crates by @jdx in
[#8325](#8325)
- **(task)** enforce per-task timeout configuration by @tvararu in
[#8250](#8250)
- **(vsix)** added vsix archives to http backend by @sosumappu in
[#8306](#8306)
- add core dotnet plugin for .NET SDK management by @jdx in
[#8326](#8326)

### 🐛 Bug Fixes

- **(conda)** preserve conda_packages on locked install and fix temp
file race by @jdx in [#8335](#8335)
- **(conda)** deduplicate repodata records to fix solver error on Linux
by @jdx in [#8337](#8337)
- **(env)** include watch_files in fast-path early exit check by @jdx in
[#8317](#8317)
- **(env)** clear fish completions when setting/unsetting shell aliases
by @jdx in [#8324](#8324)
- **(lockfile)** prevent lockfile writes when --locked is set by @jdx in
[#8308](#8308)
- **(lockfile)** prune orphan tool entries on mise lock by @mackwic in
[#8265](#8265)
- **(lockfile)** error on contradictory locked=true + lockfile=false
config by @jdx in [#8329](#8329)
- **(regal)** Update package location by @charlieegan3 in
[#8315](#8315)
- **(release)** strip markdown heading prefix from communique release
title by @jdx in [#8303](#8303)
- **(schema)** enforce additionalProperties constraint for env by
@adamliang0 in [#8328](#8328)

### 📚 Documentation

- Remove incorrect oh-my-zsh plugin ordering comment by @bvosk in
[#8323](#8323)
- require AI disclosure on GitHub comments by @jdx in
[#8330](#8330)

### 📦 Registry

- add `oxfmt` by @taoufik07 in
[#8316](#8316)

### New Contributors

- @adamliang0 made their first contribution in
[#8328](#8328)
- @tvararu made their first contribution in
[#8250](#8250)
- @bvosk made their first contribution in
[#8323](#8323)
- @taoufik07 made their first contribution in
[#8316](#8316)
- @charlieegan3 made their first contribution in
[#8315](#8315)
- @sosumappu made their first contribution in
[#8306](#8306)

## 📦 Aqua Registry Updates

#### New Packages (3)

- [`Tyrrrz/FFmpegBin`](https://github.com/Tyrrrz/FFmpegBin)
- [`elixir-lang/expert`](https://github.com/elixir-lang/expert)
- [`erikjuhani/basalt`](https://github.com/erikjuhani/basalt)

#### Updated Packages (5)

- [`caarlos0/fork-cleaner`](https://github.com/caarlos0/fork-cleaner)
-
[`firecow/gitlab-ci-local`](https://github.com/firecow/gitlab-ci-local)
- [`jackchuka/mdschema`](https://github.com/jackchuka/mdschema)
-
[`kunobi-ninja/kunobi-releases`](https://github.com/kunobi-ninja/kunobi-releases)
- [`peco/peco`](https://github.com/peco/peco)
risu729 pushed a commit to risu729/mise that referenced this pull request Feb 27, 2026
## Summary

When tools are removed from config, `mise lock` could keep stale entries
in `mise.lock`.
This PR makes full lock runs prune orphan tool entries so lockfiles stay
aligned with current config.

Follow-up fixes were added after initial review to cover subtle lockfile
compatibility cases.

## Changes

- Prune stale lockfile tool entries during full `mise lock` runs (no
tool filter).
- Keep existing behavior for filtered runs (`mise lock <tool>`), which
do not prune other tools.
- Handle the “all tools removed” case:
- even when the resolved toolset is empty, full `mise lock` now
prunes/removes stale lockfile entries.
- Preserve compatibility with legacy lockfile keying:
- pruning now keeps entries by either configured tool short name or
backend identifier
- this avoids dropping legacy entries like `[[tools.jq]]` with `backend
= "aqua:jqlang/jq"`.
- Add `Lockfile::retain_tools_by_short_or_backend(...)` and run conda
package cleanup after prune.
- Add/extend regression e2e coverage:
  - `e2e/lockfile/test_lockfile_prune_removed_tool`
    - tool replacement prune
    - remove-all-tools prune
- Keep `e2e/cli/test_lock_platform_merge` passing with legacy lockfile
shape.

## Why

Prevents orphan entries in `mise.lock`, keeps lockfiles
deterministic/config-aligned, and avoids regressions with existing
lockfile formats.

## Validation

- `cargo fmt --all --check`
- `cargo clippy -- -D warnings`
- `cargo clippy --all-features --all-targets -- -D warnings`
- `cargo test -q test_retain_tools_by_short_prunes_removed_tools`
- `cargo test -q
test_retain_tools_by_short_cleans_unreferenced_conda_packages`
- `cargo test -q
test_retain_tools_by_short_or_backend_preserves_legacy_keyed_entries`
- `cargo test -q
test_should_prune_stale_lockfile_entries_without_tool_filter`
- `cargo test -q
test_should_not_prune_stale_lockfile_entries_with_tool_filter`
- `cargo test -q
test_prune_stale_entries_with_empty_tools_prunes_all_entries`
- `cargo test -q
test_prune_stale_entries_with_filter_keeps_existing_entries`
- `cargo test -q
test_prune_stale_entries_preserves_legacy_keyed_backend_match`
- `./e2e/run_test e2e/lockfile/test_lockfile_prune_removed_tool`
- `./e2e/run_test e2e/cli/test_lock_platform_merge`

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
risu729 pushed a commit to risu729/mise that referenced this pull request Feb 27, 2026
### 🚀 Features

- **(conda)** replace custom backend with rattler crates by @jdx in
[jdx#8325](jdx#8325)
- **(task)** enforce per-task timeout configuration by @tvararu in
[jdx#8250](jdx#8250)
- **(vsix)** added vsix archives to http backend by @sosumappu in
[jdx#8306](jdx#8306)
- add core dotnet plugin for .NET SDK management by @jdx in
[jdx#8326](jdx#8326)

### 🐛 Bug Fixes

- **(conda)** preserve conda_packages on locked install and fix temp
file race by @jdx in [jdx#8335](jdx#8335)
- **(conda)** deduplicate repodata records to fix solver error on Linux
by @jdx in [jdx#8337](jdx#8337)
- **(env)** include watch_files in fast-path early exit check by @jdx in
[jdx#8317](jdx#8317)
- **(env)** clear fish completions when setting/unsetting shell aliases
by @jdx in [jdx#8324](jdx#8324)
- **(lockfile)** prevent lockfile writes when --locked is set by @jdx in
[jdx#8308](jdx#8308)
- **(lockfile)** prune orphan tool entries on mise lock by @mackwic in
[jdx#8265](jdx#8265)
- **(lockfile)** error on contradictory locked=true + lockfile=false
config by @jdx in [jdx#8329](jdx#8329)
- **(regal)** Update package location by @charlieegan3 in
[jdx#8315](jdx#8315)
- **(release)** strip markdown heading prefix from communique release
title by @jdx in [jdx#8303](jdx#8303)
- **(schema)** enforce additionalProperties constraint for env by
@adamliang0 in [jdx#8328](jdx#8328)

### 📚 Documentation

- Remove incorrect oh-my-zsh plugin ordering comment by @bvosk in
[jdx#8323](jdx#8323)
- require AI disclosure on GitHub comments by @jdx in
[jdx#8330](jdx#8330)

### 📦 Registry

- add `oxfmt` by @taoufik07 in
[jdx#8316](jdx#8316)

### New Contributors

- @adamliang0 made their first contribution in
[jdx#8328](jdx#8328)
- @tvararu made their first contribution in
[jdx#8250](jdx#8250)
- @bvosk made their first contribution in
[jdx#8323](jdx#8323)
- @taoufik07 made their first contribution in
[jdx#8316](jdx#8316)
- @charlieegan3 made their first contribution in
[jdx#8315](jdx#8315)
- @sosumappu made their first contribution in
[jdx#8306](jdx#8306)

## 📦 Aqua Registry Updates

#### New Packages (3)

- [`Tyrrrz/FFmpegBin`](https://github.com/Tyrrrz/FFmpegBin)
- [`elixir-lang/expert`](https://github.com/elixir-lang/expert)
- [`erikjuhani/basalt`](https://github.com/erikjuhani/basalt)

#### Updated Packages (5)

- [`caarlos0/fork-cleaner`](https://github.com/caarlos0/fork-cleaner)
-
[`firecow/gitlab-ci-local`](https://github.com/firecow/gitlab-ci-local)
- [`jackchuka/mdschema`](https://github.com/jackchuka/mdschema)
-
[`kunobi-ninja/kunobi-releases`](https://github.com/kunobi-ninja/kunobi-releases)
- [`peco/peco`](https://github.com/peco/peco)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants