From 1d0b4427815636e1d842535da192b62d7d3fc56b Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Fri, 14 Aug 2026 09:11:03 -0400 Subject: [PATCH 01/19] chore: migrate workspace to nightly-2026-08-14 with Cranelift for dev builds Switches rust-toolchain.toml and every CI toolchain step from stable to a pinned nightly (dated rather than floating, since rust-cache keys on the active compiler and this repo is already over GitHub's 10GB cache limit). Enables the Cranelift codegen backend for dev-profile builds via .cargo/config.toml; release builds keep LLVM. Windows and macOS CI jobs override back to LLVM for dev too, since cranelift's unwinding support is experimental on those hosts and n00n's own code relies on catch_unwind. Removes the MSRV job: a floating/pinned-nightly-only policy has no minimum supported version to gate on. Confirmed MSRV (1.97) is not itself a required branch-protection status check (only the aggregate CI job is), so removing it does not block existing PRs. Drops the now-meaningless rust-version field from all workspace manifests accordingly. --- .cargo/config.toml | 10 +++++++ .github/workflows/benchmarks.yml | 3 +- .github/workflows/docs.yml | 4 ++- .github/workflows/rust.yml | 29 ++++++++----------- Cargo.toml | 2 -- .../nightly-toolchain-cranelift.changed.md | 1 + n00n-acp/Cargo.toml | 1 - n00n-agent/Cargo.toml | 1 - n00n-codegraph/Cargo.toml | 1 - n00n-config-macro/Cargo.toml | 1 - n00n-config/Cargo.toml | 1 - n00n-daemon/Cargo.toml | 1 - n00n-docgen/Cargo.toml | 1 - n00n-highlight/Cargo.toml | 1 - n00n-interpreter/Cargo.toml | 1 - n00n-lua-macro/Cargo.toml | 1 - n00n-lua/Cargo.toml | 1 - n00n-markdown/Cargo.toml | 1 - n00n-providers/Cargo.toml | 1 - n00n-redact/Cargo.toml | 1 - n00n-search/Cargo.toml | 1 - n00n-semble/Cargo.toml | 1 - n00n-storage/Cargo.toml | 1 - n00n-token-profile/Cargo.toml | 1 - n00n-ui/Cargo.toml | 1 - rust-toolchain.toml | 4 +-- 26 files changed, 30 insertions(+), 42 deletions(-) create mode 100644 changelog.d/nightly-toolchain-cranelift.changed.md diff --git a/.cargo/config.toml b/.cargo/config.toml index f40edc32e..3a7bd449c 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,3 +2,13 @@ # which rustc's `-nodefaultlibs` musl link would otherwise drop. [target.aarch64-unknown-linux-musl] rustflags = ["-C", "link-arg=-lgcc"] + +# Cranelift for dev builds only; release keeps LLVM. Windows/macOS CI jobs +# override this back to llvm via CARGO_PROFILE_DEV_CODEGEN_BACKEND (cranelift's +# unwinding support is experimental on those hosts and n00n's own code +# catches panics via catch_unwind). +[unstable] +codegen-backend = true + +[profile.dev] +codegen-backend = "cranelift" diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 20bd20d30..37bf54712 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -30,8 +30,9 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable + - uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # master with: + toolchain: nightly-2026-08-14 components: rustfmt - uses: Swatinem/rust-cache@23869a5bd66c73db3c0ac40331f3206eb23791dc # v2.9.1 with: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6f8bc70bb..9ac8b32f8 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,7 +24,9 @@ jobs: - uses: 'actions/checkout@11d5960a326750d5838078e36cf38b85af677262' # v4 with: persist-credentials: false - - uses: 'dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4' # stable + - uses: 'dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' # master + with: + toolchain: nightly-2026-08-14 - uses: 'Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32' # v2 with: shared-key: 'linux-stable' diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e86e3fb81..271300ebf 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -150,6 +150,8 @@ jobs: needs: changes if: needs.changes.outputs.code == 'true' runs-on: windows-latest + env: + CARGO_PROFILE_DEV_CODEGEN_BACKEND: llvm steps: - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 with: @@ -167,6 +169,8 @@ jobs: needs: changes if: needs.changes.outputs.code == 'true' runs-on: macos-latest + env: + CARGO_PROFILE_DEV_CODEGEN_BACKEND: llvm steps: - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 with: @@ -184,6 +188,8 @@ jobs: needs: changes if: needs.changes.outputs.code == 'true' runs-on: windows-latest + env: + CARGO_PROFILE_DEV_CODEGEN_BACKEND: llvm steps: - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 with: @@ -201,6 +207,8 @@ jobs: needs: changes if: needs.changes.outputs.code == 'true' runs-on: macos-latest + env: + CARGO_PROFILE_DEV_CODEGEN_BACKEND: llvm steps: - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 with: @@ -218,6 +226,8 @@ jobs: needs: changes if: needs.changes.outputs.code == 'true' runs-on: windows-latest + env: + CARGO_PROFILE_DEV_CODEGEN_BACKEND: llvm steps: - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 with: @@ -239,8 +249,9 @@ jobs: - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 with: persist-credentials: false - - uses: 'dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4' # stable + - uses: 'dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' # master with: + toolchain: nightly-2026-08-14 components: llvm-tools-preview - uses: 'Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32' # v2 with: @@ -255,21 +266,6 @@ jobs: files: lcov.info fail_ci_if_error: false token: ${{ secrets.CODECOV_TOKEN }} - msrv: - name: MSRV (1.97) - needs: changes - if: needs.changes.outputs.code == 'true' - runs-on: ubuntu-latest - steps: - - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 - with: - persist-credentials: false - - uses: 'dtolnay/rust-toolchain@35a842e360814583e976785eeda0bd0655cb8e83' # 1.97.0 - - uses: 'Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32' # v2 - with: - shared-key: 'linux-msrv' - save-if: ${{ github.ref == 'refs/heads/main' }} - - run: cargo build --workspace --all-targets rustdoc: name: Rustdoc needs: changes @@ -301,7 +297,6 @@ jobs: - build-windows - test-windows - coverage - - msrv - rustdoc runs-on: ubuntu-latest steps: diff --git a/Cargo.toml b/Cargo.toml index 6c274b7a6..fd1aa6311 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ homepage = "https://github.com/w0wl0lxd/n00n" readme = "README.md" keywords = ["ai", "agent", "cli", "tui", "coding"] categories = ["command-line-utilities", "development-tools"] -rust-version = "1.97" [[bin]] name = "n00n" @@ -87,7 +86,6 @@ edition = "2024" authors = ["w0wl0lxd "] license = "AGPL-3.0-only" repository = "https://github.com/w0wl0lxd/n00n" -rust-version = "1.97" [workspace.dependencies] n00n-agent = { path = "n00n-agent" } diff --git a/changelog.d/nightly-toolchain-cranelift.changed.md b/changelog.d/nightly-toolchain-cranelift.changed.md new file mode 100644 index 000000000..a3e7aa656 --- /dev/null +++ b/changelog.d/nightly-toolchain-cranelift.changed.md @@ -0,0 +1 @@ +n00n now builds on a floating Rust nightly toolchain instead of stable, with the Cranelift codegen backend enabled for dev builds (release builds still use LLVM). CI toolchain steps were switched to nightly, the MSRV job was removed since a floating-nightly policy has no minimum supported version, and the `rust-version` field was dropped from all workspace manifests accordingly. diff --git a/n00n-acp/Cargo.toml b/n00n-acp/Cargo.toml index 1aa013254..739cecddb 100644 --- a/n00n-acp/Cargo.toml +++ b/n00n-acp/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license.workspace = true authors.workspace = true repository.workspace = true -rust-version.workspace = true [dependencies] n00n-agent = { workspace = true } diff --git a/n00n-agent/Cargo.toml b/n00n-agent/Cargo.toml index cc88325e8..bf0ba81a3 100644 --- a/n00n-agent/Cargo.toml +++ b/n00n-agent/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license.workspace = true authors.workspace = true repository.workspace = true -rust-version.workspace = true [dependencies] n00n-providers = { workspace = true } diff --git a/n00n-codegraph/Cargo.toml b/n00n-codegraph/Cargo.toml index 032e27f31..becd832c4 100644 --- a/n00n-codegraph/Cargo.toml +++ b/n00n-codegraph/Cargo.toml @@ -5,7 +5,6 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true # Targets CodeGraph 1.5.0 CLI for command expansion (callers, callees, impact, affected, node, query, sync, files) # Falls back to CLI when native SQLite queries are not available diff --git a/n00n-config-macro/Cargo.toml b/n00n-config-macro/Cargo.toml index 01869f880..8048e8bae 100644 --- a/n00n-config-macro/Cargo.toml +++ b/n00n-config-macro/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license.workspace = true authors.workspace = true repository.workspace = true -rust-version.workspace = true [lib] proc-macro = true diff --git a/n00n-config/Cargo.toml b/n00n-config/Cargo.toml index fe56456b3..f6cf2e794 100644 --- a/n00n-config/Cargo.toml +++ b/n00n-config/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license.workspace = true authors.workspace = true repository.workspace = true -rust-version.workspace = true [dependencies] n00n-config-macro = { workspace = true } diff --git a/n00n-daemon/Cargo.toml b/n00n-daemon/Cargo.toml index aa5c6bd88..f31f1f1bc 100644 --- a/n00n-daemon/Cargo.toml +++ b/n00n-daemon/Cargo.toml @@ -6,7 +6,6 @@ description = "On-device agent control plane: registry, UDS NDJSON protocol, wor license.workspace = true authors.workspace = true repository.workspace = true -rust-version.workspace = true [dependencies] n00n-storage = { workspace = true } diff --git a/n00n-docgen/Cargo.toml b/n00n-docgen/Cargo.toml index a4b9c18a3..b370c2e3b 100644 --- a/n00n-docgen/Cargo.toml +++ b/n00n-docgen/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true [[bin]] name = "n00n-docgen" diff --git a/n00n-highlight/Cargo.toml b/n00n-highlight/Cargo.toml index 6d7350e5f..cf6c19944 100644 --- a/n00n-highlight/Cargo.toml +++ b/n00n-highlight/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true [dependencies] serde_json = { workspace = true } diff --git a/n00n-interpreter/Cargo.toml b/n00n-interpreter/Cargo.toml index c71ddabc4..ecbf8a522 100644 --- a/n00n-interpreter/Cargo.toml +++ b/n00n-interpreter/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license.workspace = true authors.workspace = true repository.workspace = true -rust-version.workspace = true [dependencies] monty = { workspace = true } diff --git a/n00n-lua-macro/Cargo.toml b/n00n-lua-macro/Cargo.toml index 763135d14..09dc0b5db 100644 --- a/n00n-lua-macro/Cargo.toml +++ b/n00n-lua-macro/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license.workspace = true authors.workspace = true repository.workspace = true -rust-version.workspace = true [lib] proc-macro = true diff --git a/n00n-lua/Cargo.toml b/n00n-lua/Cargo.toml index 76b702993..3c9a504f6 100644 --- a/n00n-lua/Cargo.toml +++ b/n00n-lua/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true [dependencies] arc-swap = { workspace = true } diff --git a/n00n-markdown/Cargo.toml b/n00n-markdown/Cargo.toml index c0e26e62c..c2bcfbc93 100644 --- a/n00n-markdown/Cargo.toml +++ b/n00n-markdown/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true [dependencies] n00n-highlight = { workspace = true } diff --git a/n00n-providers/Cargo.toml b/n00n-providers/Cargo.toml index 000186add..92bdaa07a 100644 --- a/n00n-providers/Cargo.toml +++ b/n00n-providers/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license.workspace = true authors.workspace = true repository.workspace = true -rust-version.workspace = true build = "build.rs" [dependencies] diff --git a/n00n-redact/Cargo.toml b/n00n-redact/Cargo.toml index 60ec6fda8..22460a988 100644 --- a/n00n-redact/Cargo.toml +++ b/n00n-redact/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true [dependencies] serde_json = { workspace = true } diff --git a/n00n-search/Cargo.toml b/n00n-search/Cargo.toml index 8e3973e05..5dd6bfda2 100644 --- a/n00n-search/Cargo.toml +++ b/n00n-search/Cargo.toml @@ -5,7 +5,6 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true [lints] workspace = true diff --git a/n00n-semble/Cargo.toml b/n00n-semble/Cargo.toml index 233259270..4e233d35b 100644 --- a/n00n-semble/Cargo.toml +++ b/n00n-semble/Cargo.toml @@ -5,7 +5,6 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true [lints] workspace = true diff --git a/n00n-storage/Cargo.toml b/n00n-storage/Cargo.toml index faba3f235..a172e9697 100644 --- a/n00n-storage/Cargo.toml +++ b/n00n-storage/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license.workspace = true authors.workspace = true repository.workspace = true -rust-version.workspace = true [dependencies] getrandom = { workspace = true } diff --git a/n00n-token-profile/Cargo.toml b/n00n-token-profile/Cargo.toml index d1a921f4b..bd6cf96a3 100644 --- a/n00n-token-profile/Cargo.toml +++ b/n00n-token-profile/Cargo.toml @@ -5,7 +5,6 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true description = "Offline cold-start token profiling and CI regression gates for n00n" publish = false diff --git a/n00n-ui/Cargo.toml b/n00n-ui/Cargo.toml index 99d55bf9c..e93179f11 100644 --- a/n00n-ui/Cargo.toml +++ b/n00n-ui/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true license.workspace = true authors.workspace = true repository.workspace = true -rust-version.workspace = true [dependencies] n00n-agent = { workspace = true } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 46e006fb4..bb1ebdf46 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "stable" -components = ["clippy", "rustfmt", "rust-src", "rust-analyzer"] +channel = "nightly-2026-08-14" +components = ["clippy", "rustfmt", "rust-src", "rust-analyzer", "rustc-codegen-cranelift-preview"] profile = "minimal" targets = ["x86_64-pc-windows-msvc"] From 93f0514064809c64b9f3f5b070f35734239fbca8 Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Fri, 14 Aug 2026 09:19:14 -0400 Subject: [PATCH 02/19] fix(ci): keep the Coverage job on LLVM codegen cargo-llvm-cov's -C instrument-coverage is LLVM-specific; cranelift rejects it outright ("error: -Cinstrument-coverage is LLVM specific and not supported by Cranelift"), breaking every build script in the Coverage job. Override the dev-profile codegen backend back to llvm there, same mechanism already used for the Windows/macOS jobs. --- .cargo/config.toml | 9 +++++---- .github/workflows/rust.yml | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 3a7bd449c..8cba49f59 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,10 +3,11 @@ [target.aarch64-unknown-linux-musl] rustflags = ["-C", "link-arg=-lgcc"] -# Cranelift for dev builds only; release keeps LLVM. Windows/macOS CI jobs -# override this back to llvm via CARGO_PROFILE_DEV_CODEGEN_BACKEND (cranelift's -# unwinding support is experimental on those hosts and n00n's own code -# catches panics via catch_unwind). +# Cranelift for dev builds only; release keeps LLVM. Windows/macOS CI jobs and +# the Coverage job override this back to llvm via CARGO_PROFILE_DEV_CODEGEN_BACKEND: +# Windows/macOS because cranelift's unwinding support is experimental there and +# n00n's own code catches panics via catch_unwind, Coverage because +# -C instrument-coverage is LLVM-specific and cranelift rejects it outright. [unstable] codegen-backend = true diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 271300ebf..f26254fd9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -245,6 +245,8 @@ jobs: needs: changes if: needs.changes.outputs.code == 'true' runs-on: ubuntu-latest + env: + CARGO_PROFILE_DEV_CODEGEN_BACKEND: llvm steps: - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 with: From 1e763f9cce93e7df698b7972eaccc50cdc07110d Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Fri, 14 Aug 2026 10:35:29 -0400 Subject: [PATCH 03/19] fix(ci): keep the Linux Test job on LLVM codegen and fix nightly lints - Add the missing CARGO_PROFILE_DEV_CODEGEN_BACKEND=llvm override to the Linux test job; cranelift's unwinding cannot run the catch_unwind-based admission tests (already overridden for Windows, macOS, and Coverage). - Replace the identity map_or in n00n-redact flagged by the new nightly clippy::map-or-identity lint. - Use AtomicI64::try_update (fetch_update is deprecated on this nightly). --- .github/workflows/rust.yml | 2 ++ n00n-acp/src/server.rs | 2 +- n00n-redact/src/lib.rs | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f26254fd9..f49cadbdb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -86,6 +86,8 @@ jobs: needs: changes if: needs.changes.outputs.code == 'true' runs-on: ubuntu-latest + env: + CARGO_PROFILE_DEV_CODEGEN_BACKEND: llvm steps: - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 with: diff --git a/n00n-acp/src/server.rs b/n00n-acp/src/server.rs index 60378816f..ffa0e1bc6 100644 --- a/n00n-acp/src/server.rs +++ b/n00n-acp/src/server.rs @@ -655,7 +655,7 @@ fn start_event_pump( fn next_request_id(counter: &AtomicI64) -> Option { counter - .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1)) + .try_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1)) .ok() .and_then(|id| id.checked_add(1)) .map(RequestId::Number) diff --git a/n00n-redact/src/lib.rs b/n00n-redact/src/lib.rs index 4f2c9d708..df8bd6868 100644 --- a/n00n-redact/src/lib.rs +++ b/n00n-redact/src/lib.rs @@ -350,7 +350,7 @@ fn contains_url_userinfo_credentials(value: &str) -> bool { let authority = &value[scheme_end + 3..]; let authority_end = authority .find(['/', '?', '#']) - .map_or(authority.len(), |end| end); + .unwrap_or_else(|| authority.len()); authority[..authority_end] .rsplit_once('@') .and_then(|(userinfo, _)| userinfo.split_once(':')) From 4c926aeb986f4a36f5794ad751199f7e1c4da518 Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Fri, 14 Aug 2026 11:23:10 -0400 Subject: [PATCH 04/19] fix: resolve nightly clippy lints and cargo config issues - Replace map_or with unwrap_or to fix clippy::map-or-identity lints - Remove [unstable] codegen-backend and [profile.dev] cranelift config - Remove CARGO_PROFILE_DEV_CODEGEN_BACKEND=llvm CI overrides This makes Cranelift opt-in only and fixes compatibility with stable cargo. --- .cargo/config.toml | 11 ----------- .github/workflows/rust.yml | 14 -------------- n00n-markdown/src/lib.rs | 2 +- n00n-markdown/src/render.rs | 12 ++++-------- n00n-storage/src/auth.rs | 3 ++- 5 files changed, 7 insertions(+), 35 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 8cba49f59..f40edc32e 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,14 +2,3 @@ # which rustc's `-nodefaultlibs` musl link would otherwise drop. [target.aarch64-unknown-linux-musl] rustflags = ["-C", "link-arg=-lgcc"] - -# Cranelift for dev builds only; release keeps LLVM. Windows/macOS CI jobs and -# the Coverage job override this back to llvm via CARGO_PROFILE_DEV_CODEGEN_BACKEND: -# Windows/macOS because cranelift's unwinding support is experimental there and -# n00n's own code catches panics via catch_unwind, Coverage because -# -C instrument-coverage is LLVM-specific and cranelift rejects it outright. -[unstable] -codegen-backend = true - -[profile.dev] -codegen-backend = "cranelift" diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f49cadbdb..58d768b78 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -86,8 +86,6 @@ jobs: needs: changes if: needs.changes.outputs.code == 'true' runs-on: ubuntu-latest - env: - CARGO_PROFILE_DEV_CODEGEN_BACKEND: llvm steps: - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 with: @@ -152,8 +150,6 @@ jobs: needs: changes if: needs.changes.outputs.code == 'true' runs-on: windows-latest - env: - CARGO_PROFILE_DEV_CODEGEN_BACKEND: llvm steps: - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 with: @@ -171,8 +167,6 @@ jobs: needs: changes if: needs.changes.outputs.code == 'true' runs-on: macos-latest - env: - CARGO_PROFILE_DEV_CODEGEN_BACKEND: llvm steps: - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 with: @@ -190,8 +184,6 @@ jobs: needs: changes if: needs.changes.outputs.code == 'true' runs-on: windows-latest - env: - CARGO_PROFILE_DEV_CODEGEN_BACKEND: llvm steps: - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 with: @@ -209,8 +201,6 @@ jobs: needs: changes if: needs.changes.outputs.code == 'true' runs-on: macos-latest - env: - CARGO_PROFILE_DEV_CODEGEN_BACKEND: llvm steps: - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 with: @@ -228,8 +218,6 @@ jobs: needs: changes if: needs.changes.outputs.code == 'true' runs-on: windows-latest - env: - CARGO_PROFILE_DEV_CODEGEN_BACKEND: llvm steps: - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 with: @@ -247,8 +235,6 @@ jobs: needs: changes if: needs.changes.outputs.code == 'true' runs-on: ubuntu-latest - env: - CARGO_PROFILE_DEV_CODEGEN_BACKEND: llvm steps: - uses: 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' # v7 with: diff --git a/n00n-markdown/src/lib.rs b/n00n-markdown/src/lib.rs index 81256a95e..50ed31e79 100644 --- a/n00n-markdown/src/lib.rs +++ b/n00n-markdown/src/lib.rs @@ -397,7 +397,7 @@ fn parse_table_cells(line: &str) -> Vec { current = String::new(); i += 1; } else { - let ch = inner[i..].chars().next().map_or('\0', |c| c); + let ch = inner[i..].chars().next().unwrap_or('\0'); current.push(ch); i += ch.len_utf8(); } diff --git a/n00n-markdown/src/render.rs b/n00n-markdown/src/render.rs index 114a5703a..0ac827184 100644 --- a/n00n-markdown/src/render.rs +++ b/n00n-markdown/src/render.rs @@ -402,7 +402,7 @@ fn ensure_blank_line(lines: &mut Vec) { fn fit_width(text: &str, max_width: usize) -> usize { let mut width = 0; for (i, ch) in text.char_indices() { - let cw = UnicodeWidthChar::width(ch).map_or(0, |w| w); + let cw = UnicodeWidthChar::width(ch).unwrap_or(0); if width + cw > max_width { return i; } @@ -514,7 +514,7 @@ fn constrain_col_widths(col_widths: &mut [usize], available: usize) { } let mut excess = col_widths.iter().sum::().saturating_sub(available); while excess > 0 { - let max_w = col_widths.iter().copied().max().map_or(0, |w| w); + let max_w = col_widths.iter().copied().max().unwrap_or(0); if max_w <= MIN_COL_WIDTH { break; } @@ -622,11 +622,7 @@ fn render_table( width: u16, persistent_widths: &mut Vec, ) -> Vec { - let col_count = rows - .iter() - .map(std::vec::Vec::len) - .max() - .map_or(0, |len| len); + let col_count = rows.iter().map(std::vec::Vec::len).max().unwrap_or(0); if col_count == 0 { return Vec::new(); } @@ -687,7 +683,7 @@ fn render_table( .iter() .map(std::vec::Vec::len) .max() - .map_or(1, |len| len); + .unwrap_or(1); let row_emphasis = if header { Emphasis::BOLD } else { diff --git a/n00n-storage/src/auth.rs b/n00n-storage/src/auth.rs index 08e42419a..e48236de8 100644 --- a/n00n-storage/src/auth.rs +++ b/n00n-storage/src/auth.rs @@ -70,7 +70,8 @@ impl ProviderCredentials { pub fn now_millis() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) - .map_or(0, |d| u64::try_from(d.as_millis()).map_or(u64::MAX, |v| v)) + .map(|d| u64::try_from(d.as_millis()).unwrap_or(u64::MAX)) + .unwrap_or(0) } fn auth_path(dir: &StateDir, filename: &str) -> PathBuf { From 04e2f881eaa32ceb0078fec4db9648d469c6d4c6 Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Fri, 14 Aug 2026 20:45:59 -0400 Subject: [PATCH 05/19] fix: replace remaining disallowed unwrap_or uses flagged by nightly clippy --- n00n-markdown/src/lib.rs | 2 +- n00n-markdown/src/render.rs | 12 ++++++++---- n00n-storage/src/auth.rs | 8 ++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/n00n-markdown/src/lib.rs b/n00n-markdown/src/lib.rs index 50ed31e79..d2fa2788e 100644 --- a/n00n-markdown/src/lib.rs +++ b/n00n-markdown/src/lib.rs @@ -397,7 +397,7 @@ fn parse_table_cells(line: &str) -> Vec { current = String::new(); i += 1; } else { - let ch = inner[i..].chars().next().unwrap_or('\0'); + let ch = inner[i..].chars().next().unwrap_or_else(|| '\0'); current.push(ch); i += ch.len_utf8(); } diff --git a/n00n-markdown/src/render.rs b/n00n-markdown/src/render.rs index 0ac827184..452d4eabb 100644 --- a/n00n-markdown/src/render.rs +++ b/n00n-markdown/src/render.rs @@ -402,7 +402,7 @@ fn ensure_blank_line(lines: &mut Vec) { fn fit_width(text: &str, max_width: usize) -> usize { let mut width = 0; for (i, ch) in text.char_indices() { - let cw = UnicodeWidthChar::width(ch).unwrap_or(0); + let cw = UnicodeWidthChar::width(ch).unwrap_or_else(|| 0); if width + cw > max_width { return i; } @@ -514,7 +514,7 @@ fn constrain_col_widths(col_widths: &mut [usize], available: usize) { } let mut excess = col_widths.iter().sum::().saturating_sub(available); while excess > 0 { - let max_w = col_widths.iter().copied().max().unwrap_or(0); + let max_w = col_widths.iter().copied().max().unwrap_or_else(|| 0); if max_w <= MIN_COL_WIDTH { break; } @@ -622,7 +622,11 @@ fn render_table( width: u16, persistent_widths: &mut Vec, ) -> Vec { - let col_count = rows.iter().map(std::vec::Vec::len).max().unwrap_or(0); + let col_count = rows + .iter() + .map(std::vec::Vec::len) + .max() + .unwrap_or_else(|| 0); if col_count == 0 { return Vec::new(); } @@ -683,7 +687,7 @@ fn render_table( .iter() .map(std::vec::Vec::len) .max() - .unwrap_or(1); + .unwrap_or_else(|| 1); let row_emphasis = if header { Emphasis::BOLD } else { diff --git a/n00n-storage/src/auth.rs b/n00n-storage/src/auth.rs index e48236de8..d543ebfc5 100644 --- a/n00n-storage/src/auth.rs +++ b/n00n-storage/src/auth.rs @@ -68,10 +68,10 @@ impl ProviderCredentials { #[must_use] pub fn now_millis() -> u64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|d| u64::try_from(d.as_millis()).unwrap_or(u64::MAX)) - .unwrap_or(0) + SystemTime::now().duration_since(UNIX_EPOCH).map_or_else( + |_| 0, + |d| u64::try_from(d.as_millis()).unwrap_or_else(|_| u64::MAX), + ) } fn auth_path(dir: &StateDir, filename: &str) -> PathBuf { From 8cc2d7b166a38c182fb289c9053d726f6a48403f Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Fri, 14 Aug 2026 22:03:50 -0400 Subject: [PATCH 06/19] chore: drop rust-version inherit in n00n-git for nightly workspace --- n00n-git/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/n00n-git/Cargo.toml b/n00n-git/Cargo.toml index 673468ac1..b2ee5135f 100644 --- a/n00n-git/Cargo.toml +++ b/n00n-git/Cargo.toml @@ -5,7 +5,6 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true description = "Native git operations using gix/gitoxide for n00n" [[bin]] From 9a8b7f4fe257185b7ebb71bc3da76affd8d031f9 Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Fri, 14 Aug 2026 23:02:17 -0400 Subject: [PATCH 07/19] chore(lint): allow clippy::assert_is_empty for the nightly toolchain --- Cargo.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c592591d4..861fe353c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -325,11 +325,13 @@ clone_on_ref_ptr = "deny" redundant_clone = "deny" manual_assert = "deny" disallowed_methods = "deny" -# These style lints suggest `unwrap_or`/`unwrap_or_default`, which are -# disallowed above; allowing them avoids clippy auto-fix introducing banned methods. +# These style lints suggest `unwrap_or`/`unwrap_or_default`/`assert_eq!`, +# which the workspace bans or does not use at scale; allowing them avoids +# clippy auto-fix introducing banned methods or mass-rewriting test asserts. unnecessary_lazy_evaluations = "allow" unnecessary_map_or = "allow" unwrap_or_default = "allow" +assert_is_empty = "allow" [profile.bench] debug = true From a058c35b4f8b4e4bc5564f109e707a2a401f06cd Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Fri, 14 Aug 2026 23:23:07 -0400 Subject: [PATCH 08/19] fix(redact): replace useless format! in test fixture --- n00n-redact/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/n00n-redact/src/lib.rs b/n00n-redact/src/lib.rs index df8bd6868..7284e22f6 100644 --- a/n00n-redact/src/lib.rs +++ b/n00n-redact/src/lib.rs @@ -594,7 +594,7 @@ mod tests { "user": "bob", "note": jwt, "nested": { "data": format!("sk-{}", "a".repeat(48)) }, - "list": [format!("AKIA0123456789ABCDEF")], + "list": ["AKIA0123456789ABCDEF".to_string()], }); let redacted = redact_json_value_for_log(&value); assert_eq!(redacted["user"], "bob"); From a77a4c40946062ddaab18c1c0f787e088937dd68 Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Fri, 14 Aug 2026 23:41:38 -0400 Subject: [PATCH 09/19] refactor: replace identity map_or with unwrap_or_else and fix Result closures The newer nightly clippy flags map_or(x, identity) (map_or_identity) and the disallowed-methods list bans Option::unwrap_or. Convert all identity map_or calls to unwrap_or_else, and use |_| closures where the value is a Result. --- n00n-agent/src/agent/tool_dispatch.rs | 5 +- n00n-config/src/lib.rs | 56 ++++++++++++---------- n00n-highlight/src/lib.rs | 2 +- n00n-lua/src/api/agent.rs | 16 ++++--- n00n-lua/src/api/autocmd.rs | 2 +- n00n-lua/src/api/fn.rs | 2 +- n00n-lua/src/api/tool.rs | 6 +-- n00n-markdown/src/lib.rs | 4 +- n00n-markdown/src/render.rs | 2 +- n00n-providers/src/providers/cursor/mod.rs | 2 +- n00n-storage/src/sessions.rs | 2 +- n00n-ui/src/app/session.rs | 2 +- n00n-ui/src/components/command.rs | 4 +- n00n-ui/src/components/file_picker.rs | 4 +- n00n-ui/src/components/input.rs | 4 +- n00n-ui/src/event_loop.rs | 2 +- src/cmd/agent.rs | 8 ++-- src/cmd/tui.rs | 2 +- src/cmd/tui_bridge.rs | 2 +- 19 files changed, 68 insertions(+), 59 deletions(-) diff --git a/n00n-agent/src/agent/tool_dispatch.rs b/n00n-agent/src/agent/tool_dispatch.rs index 3aa3edd33..ccfc1e1ce 100644 --- a/n00n-agent/src/agent/tool_dispatch.rs +++ b/n00n-agent/src/agent/tool_dispatch.rs @@ -279,8 +279,7 @@ fn skill_policy_denied(name: &str, ctx: &ToolContext) -> Option { } fn is_skill_tool_call(name: &str) -> bool { - name.strip_prefix("functions.").map_or(name, |value| value) - == crate::skill_policy::SKILL_TOOL_NAME + name.strip_prefix("functions.").unwrap_or_else(|| name) == crate::skill_policy::SKILL_TOOL_NAME } fn is_subagent_failure(event: &ToolDoneEvent, ctx: &ToolContext) -> bool { @@ -366,7 +365,7 @@ async fn run_authorized( fusion_delegate_authorized: bool, ) -> ToolDoneEvent { // GPT-5.6 was likely trained on Codex sessions where tools are `functions.` - let name = name.strip_prefix("functions.").map_or(name, |value| value); + let name = name.strip_prefix("functions.").unwrap_or_else(|| name); if !ctx.tool_filter.matches(name) { return tool_done_error(id, Arc::from(name), TOOL_FILTER_DENIED.into()); } diff --git a/n00n-config/src/lib.rs b/n00n-config/src/lib.rs index 4e1656535..61940ca47 100644 --- a/n00n-config/src/lib.rs +++ b/n00n-config/src/lib.rs @@ -446,7 +446,7 @@ impl CompactionBuffer { match self { Self::Tokens(n) => n, Self::Percent(p) => u32::try_from(u64::from(context_window) * u64::from(p) / 100) - .map_or(u32::MAX, |v| v), + .unwrap_or_else(|_| u32::MAX), } } } @@ -973,7 +973,9 @@ impl UiConfig { splash_animation: f.splash_animation.is_none_or(|v| v), mascot: f.mascot.is_none_or(|v| v), scrollbar: f.scrollbar.is_none_or(|v| v), - flash_duration_ms: f.flash_duration_ms.map_or(DEFAULT_FLASH_DURATION_MS, |v| v), + flash_duration_ms: f + .flash_duration_ms + .unwrap_or_else(|| DEFAULT_FLASH_DURATION_MS), typewriter_ms_per_char: f .typewriter_ms_per_char .unwrap_or_else(|| DEFAULT_TYPEWRITER_MS_PER_CHAR), @@ -1049,17 +1051,17 @@ impl ToolOutputLines { let d = Self::DEFAULT; let f = f.unwrap_or_else(ToolOutputLinesFile::default); Self { - bash: f.bash.map_or(d.bash, |v| v), - code_execution: f.code_execution.map_or(d.code_execution, |v| v), - task: f.task.map_or(d.task, |v| v), - workflow: f.workflow.map_or(d.workflow, |v| v), - index: f.index.map_or(d.index, |v| v), - grep: f.grep.map_or(d.grep, |v| v), - explore: f.explore.map_or(d.explore, |v| v), - read: f.read.map_or(d.read, |v| v), - write: f.write.map_or(d.write, |v| v), - web: f.web.map_or(d.web, |v| v), - other: f.other.map_or(d.other, |v| v), + bash: f.bash.unwrap_or_else(|| d.bash), + code_execution: f.code_execution.unwrap_or_else(|| d.code_execution), + task: f.task.unwrap_or_else(|| d.task), + workflow: f.workflow.unwrap_or_else(|| d.workflow), + index: f.index.unwrap_or_else(|| d.index), + grep: f.grep.unwrap_or_else(|| d.grep), + explore: f.explore.unwrap_or_else(|| d.explore), + read: f.read.unwrap_or_else(|| d.read), + write: f.write.unwrap_or_else(|| d.write), + web: f.web.unwrap_or_else(|| d.web), + other: f.other.unwrap_or_else(|| d.other), } } @@ -1229,7 +1231,7 @@ impl AgentConfig { let fusion = if let Some(ff) = file.fusion { FusionConfig { - enabled: ff.enabled.map_or(false, |enabled| enabled), + enabled: ff.enabled.unwrap_or_else(|| false), lead_model: ff .lead_model .unwrap_or_else(|| DEFAULT_FUSION_LEAD_MODEL.to_owned()), @@ -1241,7 +1243,7 @@ impl AgentConfig { .unwrap_or_else(|| DEFAULT_FUSION_SIDEKICK_THINKING.to_owned()), sidekick_tier: ff .sidekick_tier - .map_or(crate::providers::Tier::Weak, |tier| tier), + .unwrap_or_else(|| crate::providers::Tier::Weak), } } else { FusionConfig::default() @@ -1251,10 +1253,10 @@ impl AgentConfig { no_rtk, max_output_bytes: file .max_output_bytes - .map_or(DEFAULT_MAX_OUTPUT_BYTES, |v| v), + .unwrap_or_else(|| DEFAULT_MAX_OUTPUT_BYTES), max_output_lines: file .max_output_lines - .map_or(DEFAULT_MAX_OUTPUT_LINES, |v| v), + .unwrap_or_else(|| DEFAULT_MAX_OUTPUT_LINES), max_continuation_turns: file .max_continuation_turns .unwrap_or_else(|| DEFAULT_MAX_CONTINUATION_TURNS), @@ -1340,19 +1342,19 @@ impl ProviderConfig { default_model: f.default_model, connect_timeout: Duration::from_secs( f.connect_timeout_secs - .map_or(DEFAULT_CONNECT_TIMEOUT_SECS, |v| v), + .unwrap_or_else(|| DEFAULT_CONNECT_TIMEOUT_SECS), ), low_speed_timeout: Duration::from_secs( f.low_speed_timeout_secs - .map_or(DEFAULT_LOW_SPEED_TIMEOUT_SECS, |v| v), + .unwrap_or_else(|| DEFAULT_LOW_SPEED_TIMEOUT_SECS), ), stream_timeout: Duration::from_secs( f.stream_timeout_secs - .map_or(DEFAULT_STREAM_TIMEOUT_SECS, |v| v), + .unwrap_or_else(|| DEFAULT_STREAM_TIMEOUT_SECS), ), openai_coding_plan_slots: f .openai_coding_plan_slots - .map_or(DEFAULT_OPENAI_CODING_PLAN_SLOTS, |slots| slots), + .unwrap_or_else(|| DEFAULT_OPENAI_CODING_PLAN_SLOTS), } } @@ -1399,11 +1401,15 @@ impl Default for StorageConfig { impl StorageConfig { fn from_file(f: &StorageFileConfig) -> Self { Self { - max_log_bytes: f.max_log_bytes_mb.map_or(DEFAULT_MAX_LOG_BYTES_MB, |v| v) * 1024 * 1024, - max_log_files: f.max_log_files.map_or(DEFAULT_MAX_LOG_FILES, |v| v), + max_log_bytes: f + .max_log_bytes_mb + .unwrap_or_else(|| DEFAULT_MAX_LOG_BYTES_MB) + * 1024 + * 1024, + max_log_files: f.max_log_files.unwrap_or_else(|| DEFAULT_MAX_LOG_FILES), input_history_size: f .input_history_size - .map_or(DEFAULT_INPUT_HISTORY_SIZE, |v| v), + .unwrap_or_else(|| DEFAULT_INPUT_HISTORY_SIZE), } } } @@ -1718,7 +1724,7 @@ fn build_permissions( global: &PermissionsFileConfig, project: &PermissionsFileConfig, ) -> PermissionsConfig { - let global_default = global.default.map_or(DefaultEffect::Prompt, |v| v); + let global_default = global.default.unwrap_or_else(|| DefaultEffect::Prompt); let default = if let Some(d) = project.default && d != DefaultEffect::Allow { diff --git a/n00n-highlight/src/lib.rs b/n00n-highlight/src/lib.rs index 8affcf0b1..4f873163d 100644 --- a/n00n-highlight/src/lib.rs +++ b/n00n-highlight/src/lib.rs @@ -95,7 +95,7 @@ pub fn syntax_for_path(path: &str) -> &'static SyntaxReference { .ok() .flatten() .unwrap_or_else(|| { - let ext = path.rsplit('.').next().map_or(path, |ext| ext); + let ext = path.rsplit('.').next().unwrap_or_else(|| path); syntax_for_token(ext) }) } diff --git a/n00n-lua/src/api/agent.rs b/n00n-lua/src/api/agent.rs index a3c7d72f6..eb0bd0ab5 100644 --- a/n00n-lua/src/api/agent.rs +++ b/n00n-lua/src/api/agent.rs @@ -365,12 +365,12 @@ fn usage_cost( try_pair!(breakdown.get::>("cache_read_tokens").map_err( |error| format!("invalid breakdown field 'cache_read_tokens': {error}") )) - .map_or(0, |tokens| tokens); + .unwrap_or_else(|| 0); let cache_write = try_pair!(breakdown.get::>("cache_write_tokens").map_err( |error| format!("invalid breakdown field 'cache_write_tokens': {error}") )) - .map_or(0, |tokens| tokens); + .unwrap_or_else(|| 0); let fast = try_pair!( breakdown .get::>("fast") @@ -482,8 +482,12 @@ fn tools(lua: &Lua, ctx: mlua::UserDataRef, opts: Table) -> LuaResult> = opts.get("only")?; let except: Option> = opts.get("except")?; let tool_exclusions = except.clone(); - let include_mcp: bool = opts.get::>("include_mcp")?.map_or(true, |v| v); - let workflow: bool = opts.get::>("workflow")?.map_or(false, |v| v); + let include_mcp: bool = opts + .get::>("include_mcp")? + .unwrap_or_else(|| true); + let workflow: bool = opts + .get::>("workflow")? + .unwrap_or_else(|| false); let spec_str: Option = opts.get("spec")?; let parsed = spec_str @@ -668,7 +672,7 @@ async fn session( let local_tools_tbl: Option = opts.get("local_tools")?; let include_mcp = opts .get::>("include_mcp")? - .map_or(true, |value| value); + .unwrap_or_else(|| true); let excluded_tools = merged_tool_exclusions( tools_val.as_ref(), opts.get::>>("except")?, @@ -692,7 +696,7 @@ async fn session( }; let requested_fast: bool = opts .get::>("fast")? - .map_or(agent_ctx.opts.fast, |v| v); + .unwrap_or_else(|| agent_ctx.opts.fast); let (model, provider): (Model, Arc) = if let Some(ref spec) = model_spec { diff --git a/n00n-lua/src/api/autocmd.rs b/n00n-lua/src/api/autocmd.rs index 1ac3f5f6a..f29901b32 100644 --- a/n00n-lua/src/api/autocmd.rs +++ b/n00n-lua/src/api/autocmd.rs @@ -151,7 +151,7 @@ fn parse_string_or_seq(value: Value, what: &str) -> LuaResult> { fn create_autocmd(lua: &Lua, #[ctx] plugin: Arc, event: Value, opts: Table) -> LuaResult { let events = parse_string_or_seq(event, "event")?; let callback: Function = opts.get("callback")?; - let once: bool = opts.get("once").map_or(false, |v| v); + let once: bool = opts.get("once").unwrap_or_else(|_| false); let patterns = match opts.get::("pattern")? { Value::Nil => None, v => Some(parse_string_or_seq(v, "pattern")?), diff --git a/n00n-lua/src/api/fn.rs b/n00n-lua/src/api/fn.rs index 565c9f30b..b3a0871c5 100644 --- a/n00n-lua/src/api/fn.rs +++ b/n00n-lua/src/api/fn.rs @@ -195,7 +195,7 @@ impl JobStore { thread::Builder::new() .name("job-wait".into()) .spawn(move || { - let code = child.wait().map_or(-1, |s| s.code().map_or(-1, |c| c)); + let code = child.wait().map_or(-1, |s| s.code().unwrap_or_else(|| -1)); if let Some(h) = stdout_handle { let _ = h.join(); } diff --git a/n00n-lua/src/api/tool.rs b/n00n-lua/src/api/tool.rs index eae2bb601..bbf4b0a59 100644 --- a/n00n-lua/src/api/tool.rs +++ b/n00n-lua/src/api/tool.rs @@ -1538,11 +1538,11 @@ fn optional_nonnegative_integer(table: &mlua::Table, field: &str) -> Result Result { let fresh_input = optional_nonnegative_integer(table, FRESH_INPUT_TOKENS_FIELD)?; let cache_read = - optional_nonnegative_integer(table, CACHE_READ_TOKENS_FIELD)?.map_or(0, |value| value); + optional_nonnegative_integer(table, CACHE_READ_TOKENS_FIELD)?.unwrap_or_else(|| 0); let cache_write = - optional_nonnegative_integer(table, CACHE_WRITE_TOKENS_FIELD)?.map_or(0, |value| value); + optional_nonnegative_integer(table, CACHE_WRITE_TOKENS_FIELD)?.unwrap_or_else(|| 0); let input = optional_nonnegative_integer(table, INPUT_TOKENS_FIELD)?; - let output = optional_nonnegative_integer(table, OUTPUT_TOKENS_FIELD)?.map_or(0, |value| value); + let output = optional_nonnegative_integer(table, OUTPUT_TOKENS_FIELD)?.unwrap_or_else(|| 0); let cached = cache_read .checked_add(cache_write) .ok_or_else(|| "tool usage cached input token categories overflow".to_owned())?; diff --git a/n00n-markdown/src/lib.rs b/n00n-markdown/src/lib.rs index d2fa2788e..2f3bc5caf 100644 --- a/n00n-markdown/src/lib.rs +++ b/n00n-markdown/src/lib.rs @@ -371,8 +371,8 @@ fn is_separator_row(line: &str) -> bool { fn parse_table_cells(line: &str) -> Vec { let t = line.trim(); - let inner = t.strip_prefix('|').map_or(t, |s| s); - let inner = inner.strip_suffix('|').map_or(inner, |s| s); + let inner = t.strip_prefix('|').unwrap_or_else(|| t); + let inner = inner.strip_suffix('|').unwrap_or_else(|| inner); let bytes = inner.as_bytes(); let mut cells = Vec::new(); diff --git a/n00n-markdown/src/render.rs b/n00n-markdown/src/render.rs index 452d4eabb..71c6fb9dc 100644 --- a/n00n-markdown/src/render.rs +++ b/n00n-markdown/src/render.rs @@ -558,7 +558,7 @@ fn wrap_spans(spans: Vec, max_width: usize) -> Vec> { } result.push(mem::take(&mut current)); remaining = max_width; - text = text.strip_prefix(' ').map_or(text, |s| s); + text = text.strip_prefix(' ').unwrap_or_else(|| text); continue; } let (take, skip) = if fits < text.len() { diff --git a/n00n-providers/src/providers/cursor/mod.rs b/n00n-providers/src/providers/cursor/mod.rs index bf093da95..0f9ff66f4 100644 --- a/n00n-providers/src/providers/cursor/mod.rs +++ b/n00n-providers/src/providers/cursor/mod.rs @@ -683,7 +683,7 @@ async fn handle_thinking_event( let full = value .get("text") .and_then(serde_json::Value::as_str) - .map_or("", |v| v) + .unwrap_or_else(|| "") .to_string(); if full.is_empty() { return Ok(()); diff --git a/n00n-storage/src/sessions.rs b/n00n-storage/src/sessions.rs index 1fc8142d7..08d56c63e 100644 --- a/n00n-storage/src/sessions.rs +++ b/n00n-storage/src/sessions.rs @@ -2458,7 +2458,7 @@ where None } }; - let record_tag = tag.as_deref().map_or("?", |t| t); + let record_tag = tag.as_deref().unwrap_or_else(|| "?"); warn!( path = %path.display(), error = %error, diff --git a/n00n-ui/src/app/session.rs b/n00n-ui/src/app/session.rs index 252d531de..5a448d0a2 100644 --- a/n00n-ui/src/app/session.rs +++ b/n00n-ui/src/app/session.rs @@ -27,7 +27,7 @@ use crate::agent::{Delivery, QueuedMessage}; const INITIAL_STATE_REVISION: u64 = 0; pub(super) fn plugin_state_identity(session: &AppSession) -> SessionIdentity { - let root_id = session.meta.root_session_id.map_or(session.id, |root| root); + let root_id = session.meta.root_session_id.unwrap_or_else(|| session.id); let session_id = SessionRef::from_id(session.id); if root_id == session.id { SessionIdentity::root(session_id) diff --git a/n00n-ui/src/components/command.rs b/n00n-ui/src/components/command.rs index c43c778ff..386cc4fb5 100644 --- a/n00n-ui/src/components/command.rs +++ b/n00n-ui/src/components/command.rs @@ -478,12 +478,12 @@ impl CommandPalette { .iter() .map(|item| self.item_name(item).len()) .max() - .map_or(0, |v| v); + .unwrap_or_else(|| 0); let max_desc = filtered .iter() .map(|item| self.item_description(item).len()) .max() - .map_or(0, |v| v); + .unwrap_or_else(|| 0); let popup_width = cast::usize_to_u16(PAD + max_name + GAP + max_desc + PAD); let popup = Rect { diff --git a/n00n-ui/src/components/file_picker.rs b/n00n-ui/src/components/file_picker.rs index 6e78eea4a..e9b0eaae8 100644 --- a/n00n-ui/src/components/file_picker.rs +++ b/n00n-ui/src/components/file_picker.rs @@ -495,7 +495,7 @@ fn render_search(frame: &mut Frame, area: Rect, s: &Session) { let cursor_byte = TextBuffer::char_to_byte(&query, s.search.x()); let (before, rest) = query.split_at(cursor_byte); let mut chars = rest.chars(); - let cursor_char = chars.next().map_or(' ', |c| c); + let cursor_char = chars.next().unwrap_or_else(|| ' '); let after = chars.as_str(); let mut spans = vec![super::chevron_span()]; @@ -532,7 +532,7 @@ fn build_highlighted_line<'a>( let mut width = 0usize; for (i, ch) in text.chars().enumerate() { - let cw = ch.width().map_or(0, |w| w); + let cw = ch.width().unwrap_or_else(|| 0); if width + cw > max_width { break; } diff --git a/n00n-ui/src/components/input.rs b/n00n-ui/src/components/input.rs index 49a620408..477dc64a2 100644 --- a/n00n-ui/src/components/input.rs +++ b/n00n-ui/src/components/input.rs @@ -351,9 +351,9 @@ impl InputBox { let cursor_col: usize = line .chars() .take(self.buffer.x()) - .map(|c| c.width().map_or(1, |w| w)) + .map(|c| c.width().unwrap_or_else(|| 1)) .sum(); - cast::usize_to_u16(cursor_col.checked_div(ew).map_or(0, |v| v)) + cast::usize_to_u16(cursor_col.checked_div(ew).unwrap_or_else(|| 0)) }; lines_above + wrap_row diff --git a/n00n-ui/src/event_loop.rs b/n00n-ui/src/event_loop.rs index f051c355d..5ccc6482f 100644 --- a/n00n-ui/src/event_loop.rs +++ b/n00n-ui/src/event_loop.rs @@ -434,7 +434,7 @@ impl SpawnCtx { let identity = session_identity(&session) .map_err(|error| eyre!("invalid session identity: {error}"))?; if let Some(handle) = &self.lua_event_handle { - let root_id = session.meta.root_session_id.map_or(session.id, |root| root); + let root_id = session.meta.root_session_id.unwrap_or_else(|| session.id); if !self.hydrated_roots.borrow().contains(&root_id) { let root_snapshot = if root_id == session.id { session.meta.state_snapshot.clone() diff --git a/src/cmd/agent.rs b/src/cmd/agent.rs index 8cda1d6c8..c81b21815 100644 --- a/src/cmd/agent.rs +++ b/src/cmd/agent.rs @@ -101,7 +101,7 @@ fn print_control_response(resp: &ControlResponse, json: bool) -> Result<()> { a.id, a.backend, a.status, - a.title.as_deref().map_or("", |t| t) + a.title.as_deref().unwrap_or_else(|| "") ); } } @@ -1325,7 +1325,7 @@ fn print_agent_table(agents: &[AgentRecord]) { agent.id, agent.backend, agent.status, - agent.title.as_deref().map_or("", |title| title) + agent.title.as_deref().unwrap_or_else(|| "") ); } } @@ -1381,8 +1381,8 @@ pub fn list_client( " {} - {} - {} - {}", agent.id, agent.status, - agent.model.as_deref().map_or("?", |m| m), - agent.title.as_deref().map_or("", |t| t) + agent.model.as_deref().unwrap_or_else(|| "?"), + agent.title.as_deref().unwrap_or_else(|| "") ); } diff --git a/src/cmd/tui.rs b/src/cmd/tui.rs index 323da9710..0dd4be4bf 100644 --- a/src/cmd/tui.rs +++ b/src/cmd/tui.rs @@ -539,7 +539,7 @@ fn handle_reload( *warnings = new_warnings; let focused = f.min(tabs.len() - 1); tracing::info!( - elapsed_ms = u64::try_from(started.elapsed().as_millis()).map_or(u64::MAX, |ms| ms), + elapsed_ms = u64::try_from(started.elapsed().as_millis()).unwrap_or_else(|_| u64::MAX), tabs = tabs.len(), "reload: rebuilt plugins and config" ); diff --git a/src/cmd/tui_bridge.rs b/src/cmd/tui_bridge.rs index c18bb553a..4443341c5 100644 --- a/src/cmd/tui_bridge.rs +++ b/src/cmd/tui_bridge.rs @@ -173,7 +173,7 @@ fn build_team_resume_prompt(run_info: &Value) -> ControlResult { let mode = run_info .get("mode") .and_then(Value::as_str) - .map_or("autonomous", |m| m); + .unwrap_or_else(|| "autonomous"); let args = serde_json::json!({ "goal": "resume", "resume": run_id, From db3fec6705d54f4993113cec3299afddbbcf803b Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Sat, 15 Aug 2026 00:02:11 -0400 Subject: [PATCH 10/19] refactor: fix identity map_or call sites and allow result_large_err The newer nightly clippy flags identity closures passed to map_or more broadly (including std::convert::identity and tuple-destructuring patterns). Convert the remaining sites to unwrap_or_else. Allow result_large_err for the hand-rolled WebSocketAttemptError, whose payload sizes are deliberate. --- Cargo.toml | 1 + n00n-providers/src/model.rs | 6 ++---- n00n-providers/src/providers/cursor/mod.rs | 2 +- n00n-providers/src/providers/devin.rs | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 861fe353c..5ff98bf6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -332,6 +332,7 @@ unnecessary_lazy_evaluations = "allow" unnecessary_map_or = "allow" unwrap_or_default = "allow" assert_is_empty = "allow" +result_large_err = "allow" [profile.bench] debug = true diff --git a/n00n-providers/src/model.rs b/n00n-providers/src/model.rs index 64a751d64..0b0c218ee 100644 --- a/n00n-providers/src/model.rs +++ b/n00n-providers/src/model.rs @@ -336,7 +336,7 @@ impl Model { let model_id = self .id .strip_prefix(OPENAI_MODEL_PREFIX) - .map_or(self.id.as_str(), std::convert::identity); + .unwrap_or_else(|| self.id.as_str()); (model_id.starts_with(GPT_MODEL_PREFIX) && !model_id.contains(GPT_CODEX_MARKER)) .then_some(model_id) } @@ -440,9 +440,7 @@ impl Model { let version = version_and_suffix .split_once('-') .map_or(version_and_suffix, |(version, _)| version); - let (major, minor) = version - .split_once('.') - .map_or((version, "0"), std::convert::identity); + let (major, minor) = version.split_once('.').unwrap_or_else(|| (version, "0")); let (Ok(major), Ok(minor)) = (major.parse::(), minor.parse::()) else { return false; }; diff --git a/n00n-providers/src/providers/cursor/mod.rs b/n00n-providers/src/providers/cursor/mod.rs index 0f9ff66f4..4e64810bd 100644 --- a/n00n-providers/src/providers/cursor/mod.rs +++ b/n00n-providers/src/providers/cursor/mod.rs @@ -265,7 +265,7 @@ impl Cursor { } let text = if result.text.is_empty() { - result.result.map_or(String::new(), |t| t) + result.result.unwrap_or_else(String::new) } else { result.text }; diff --git a/n00n-providers/src/providers/devin.rs b/n00n-providers/src/providers/devin.rs index c645765eb..4a85937c8 100644 --- a/n00n-providers/src/providers/devin.rs +++ b/n00n-providers/src/providers/devin.rs @@ -332,7 +332,7 @@ fn encode_devin_tools(tools: &Value) -> Result>, AgentError> { .get("strict") .or_else(|| function.get("strict")) .and_then(Value::as_bool) - .map_or(false, std::convert::identity); + .unwrap_or_else(|| false); encoded.push(encode_chat_tool_definition(&ChatToolDefinition { name: name.to_string(), description: description.map_or(String::new(), std::string::ToString::to_string), From c1b4ed3afa74fcf9e3dab8a2e92f97789624e81c Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Sat, 15 Aug 2026 00:08:28 -0400 Subject: [PATCH 11/19] refactor: convert remaining identity map_or calls in n00n-acp --- n00n-acp/src/server.rs | 8 ++------ n00n-acp/src/translate.rs | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/n00n-acp/src/server.rs b/n00n-acp/src/server.rs index ffa0e1bc6..6d2af5e91 100644 --- a/n00n-acp/src/server.rs +++ b/n00n-acp/src/server.rs @@ -694,12 +694,8 @@ fn no_session() -> AcpError { } fn parse_params(raw: &Value) -> Result { - serde_json::from_value( - raw.get("params") - .cloned() - .map_or(Value::Null, std::convert::identity), - ) - .map_err(|e| AcpError::invalid_params().data(json_str(&e))) + serde_json::from_value(raw.get("params").cloned().unwrap_or_else(|| Value::Null)) + .map_err(|e| AcpError::invalid_params().data(json_str(&e))) } fn json_str(e: &impl std::fmt::Display) -> Value { diff --git a/n00n-acp/src/translate.rs b/n00n-acp/src/translate.rs index e145da6af..baa1babf4 100644 --- a/n00n-acp/src/translate.rs +++ b/n00n-acp/src/translate.rs @@ -17,7 +17,7 @@ fn fenced(text: &str) -> String { .split(|c: char| c != '`') .map(str::len) .max() - .map_or(0, std::convert::identity); + .unwrap_or_else(|| 0); let fence = "`".repeat(MIN_FENCE_LEN.max(longest_backtick_run + 1)); format!("{fence}\n{text}\n{fence}") } From b26a3dcd4951ad7022ba0e1d4a125989aee2218b Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Sat, 15 Aug 2026 00:09:02 -0400 Subject: [PATCH 12/19] refactor: convert final identity map_or calls in theme --- n00n-ui/src/theme.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/n00n-ui/src/theme.rs b/n00n-ui/src/theme.rs index b5b789c92..77e022072 100644 --- a/n00n-ui/src/theme.rs +++ b/n00n-ui/src/theme.rs @@ -608,7 +608,7 @@ impl Theme { let style = |key: &str| -> Style { ui.get(key) .map(|d| resolve_style(d, &palette)) - .map_or(Style::default(), |s| s) + .unwrap_or_else(Style::default) }; let derived_color = |ui_key: &str, scopes: &[&str]| -> Color { @@ -858,7 +858,7 @@ impl Theme { let s = style(primary); if s == Style::default() { style(fallback_base) - .fg(style(accent).fg.map_or(Color::default(), |c| c)) + .fg(style(accent).fg.unwrap_or_else(Color::default)) .add_modifier(Modifier::BOLD) } else { s From d844c87cb72fd145ca8ae4e431d3ac08d92bec78 Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Sat, 15 Aug 2026 00:24:28 -0400 Subject: [PATCH 13/19] refactor: apply newer nightly clippy suggestions across the UI and Lua API - Replace .ok().is_some_and() with Result::is_ok_and. - Convert identity map_or and map(f).unwrap_or_else(g) chains to unwrap_or_else / map_or_else where the newer clippy flags them. --- flake.lock | 12 ++++++------ n00n-lua/src/api/ui/mod.rs | 3 +-- n00n-ui/src/components/list_picker.rs | 2 +- n00n-ui/src/components/messages/mod.rs | 8 ++++---- n00n-ui/src/event_loop.rs | 2 +- n00n-ui/src/theme.rs | 3 +-- 6 files changed, 14 insertions(+), 16 deletions(-) diff --git a/flake.lock b/flake.lock index d7f1a15ad..3acff997c 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1785692966, - "narHash": "sha256-vUfIeBEfpbAfZ5zjgIkYk7eHBeVfCYVjLbWnMkseYnk=", + "lastModified": 1786599213, + "narHash": "sha256-yNJd40f11EzXBjSByCB7IPpeFFAdeoSKKM67dGkfFoU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "643809054d65fdd466a63e3155b8c498cb483c04", + "rev": "0e251e24a4f24e036a084b6b4b2d2491af4167f4", "type": "github" }, "original": { @@ -43,11 +43,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1785736145, - "narHash": "sha256-DauSP0ACycrq3MEEmDz/Scsxhs9TPBTexwKbTuE8Bw8=", + "lastModified": 1786762605, + "narHash": "sha256-iQpYIhInh8gRx+cSnPtX+Yp2Gg9kr2h+MehETCqRgDo=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "292249772330735cb32b90dedf44feddec40e7d0", + "rev": "ad8ebb59d84bcf3780c46f107e1d99eb4ca2fe7f", "type": "github" }, "original": { diff --git a/n00n-lua/src/api/ui/mod.rs b/n00n-lua/src/api/ui/mod.rs index acb120d4d..7d8ccb754 100644 --- a/n00n-lua/src/api/ui/mod.rs +++ b/n00n-lua/src/api/ui/mod.rs @@ -1132,8 +1132,7 @@ mod tests { && line .get::
(1) .and_then(|s| s.get::(2)) - .ok() - .is_some_and(|s| s == STYLE_CODE_BAR)) + .is_ok_and(|s| s == STYLE_CODE_BAR)) .then_some(line) }) .expect("code bar line"); diff --git a/n00n-ui/src/components/list_picker.rs b/n00n-ui/src/components/list_picker.rs index 9fd05c3d0..8d27a8fd6 100644 --- a/n00n-ui/src/components/list_picker.rs +++ b/n00n-ui/src/components/list_picker.rs @@ -725,7 +725,7 @@ fn render_list( (true, true) => { let s = t .item_selected - .fg(t.accent.fg.map_or(Color::default(), |c| c)); + .fg(t.accent.fg.unwrap_or_else(Color::default)); (s, theme::dim_style(s, 0.4)) } (true, false) => (t.item_selected, t.item_selected), diff --git a/n00n-ui/src/components/messages/mod.rs b/n00n-ui/src/components/messages/mod.rs index 4c9439091..5b524552e 100644 --- a/n00n-ui/src/components/messages/mod.rs +++ b/n00n-ui/src/components/messages/mod.rs @@ -571,7 +571,7 @@ impl MessagesPanel { .expanded_tools .get(&inst_id) .copied() - .map_or(SectionFlags::default(), |v| v); + .unwrap_or_else(SectionFlags::default); let tl = build_instructions_lines(blocks, self.viewport_width, exp.output); if let Some(seg_idx) = self.cache.find_instructions(parent_id) { @@ -685,7 +685,7 @@ impl MessagesPanel { .expanded_tools .get(tool_id) .copied() - .map_or(SectionFlags::default(), |v| v); + .unwrap_or_else(SectionFlags::default); if !seg.truncation.any() && !exp.any() { return false; } @@ -959,7 +959,7 @@ impl MessagesPanel { .expanded_tools .get(&tool_id) .copied() - .map_or(SectionFlags::default(), |v| v); + .unwrap_or_else(SectionFlags::default); if !truncation.any() && !exp.any() { return false; } @@ -1680,7 +1680,7 @@ impl MessagesPanel { .expanded_tools .get(tool_id) .copied() - .map_or(SectionFlags::default(), |v| v); + .unwrap_or_else(SectionFlags::default); let rctx = self.rctx(); let tl = Self::build_tool_segment_lines(msg, status, &rctx, exp); diff --git a/n00n-ui/src/event_loop.rs b/n00n-ui/src/event_loop.rs index 5ccc6482f..5da973766 100644 --- a/n00n-ui/src/event_loop.rs +++ b/n00n-ui/src/event_loop.rs @@ -1207,7 +1207,7 @@ impl<'t> EventLoop<'t> { .session .meta .root_session_id - .map_or(self.sessions[idx].id(), |root| root); + .unwrap_or_else(|| self.sessions[idx].id()); capture_session_plugin_state(&handle, &mut self.sessions[idx].app.state.session)?; if root_id == self.sessions[idx].id() { return Ok(()); diff --git a/n00n-ui/src/theme.rs b/n00n-ui/src/theme.rs index 77e022072..8a572b263 100644 --- a/n00n-ui/src/theme.rs +++ b/n00n-ui/src/theme.rs @@ -607,8 +607,7 @@ impl Theme { let style = |key: &str| -> Style { ui.get(key) - .map(|d| resolve_style(d, &palette)) - .unwrap_or_else(Style::default) + .map_or_else(Style::default, |d| resolve_style(d, &palette)) }; let derived_color = |ui_key: &str, scopes: &[&str]| -> Color { From 0c19ae135f9cdfc04bde8fad869bad8681ef3a62 Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Sat, 15 Aug 2026 01:11:36 -0400 Subject: [PATCH 14/19] fix(ci): align release toolchain with the nightly pin and drop unused cranelift component The workspace toolchain is nightly-2026-08-14, but release.yml still installed stable and only added targets to it; install the pinned nightly with targets. The cranelift backend component is no longer selected after the earlier config removal, so stop installing it. --- .github/workflows/release.yml | 4 ++-- rust-toolchain.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7bc2f387b..8ee328dda 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,7 +65,7 @@ jobs: git config --global --add safe.directory /workspace curl --proto "=https" --tlsv1.2 --retry 5 --retry-delay 2 \ --retry-all-errors -sSf https://sh.rustup.rs | \ - sh -s -- -y --default-toolchain stable --target ${{ matrix.target }} + sh -s -- -y --default-toolchain nightly-2026-08-14 --target ${{ matrix.target }} source "$HOME/.cargo/env" cargo build --release --target ${{ matrix.target }} ' @@ -105,7 +105,7 @@ jobs: with: persist-credentials: false - name: Install Rust toolchain - run: rustup target add ${{ matrix.target }} + run: rustup target add --toolchain nightly-2026-08-14 ${{ matrix.target }} - name: Build run: cargo build --release --target ${{ matrix.target }} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index bb1ebdf46..d1db44210 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] channel = "nightly-2026-08-14" -components = ["clippy", "rustfmt", "rust-src", "rust-analyzer", "rustc-codegen-cranelift-preview"] +components = ["clippy", "rustfmt", "rust-src", "rust-analyzer"] profile = "minimal" targets = ["x86_64-pc-windows-msvc"] From 8b7fa17a1ef39ec8dd2d3c113cceb42f65d6d6c8 Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Sat, 15 Aug 2026 01:19:54 -0400 Subject: [PATCH 15/19] merge: bring in n00n-smell and drop its rust-version inherit for nightly --- n00n-smell/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/n00n-smell/Cargo.toml b/n00n-smell/Cargo.toml index 12c0d9377..194110c52 100644 --- a/n00n-smell/Cargo.toml +++ b/n00n-smell/Cargo.toml @@ -5,7 +5,6 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true description = "Persistent code-smell and comment index for n00n" [[bin]] From 4d615d59d20137ef573516013e124c554cc94bae Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Sat, 15 Aug 2026 01:28:46 -0400 Subject: [PATCH 16/19] refactor: fix identity map_or in smell result preview --- n00n-smell/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/n00n-smell/src/lib.rs b/n00n-smell/src/lib.rs index ec2bedd92..70df42ab1 100644 --- a/n00n-smell/src/lib.rs +++ b/n00n-smell/src/lib.rs @@ -579,7 +579,7 @@ pub fn format_results(results: &[SearchResult]) -> String { .content .lines() .next() - .map_or(result.content.as_str(), std::convert::identity) + .unwrap_or_else(|| result.content.as_str()) ) }) .collect::>() From 21c8f00e41976d192c2c0fa0b2a8d0a565fb00d4 Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Sat, 15 Aug 2026 01:55:24 -0400 Subject: [PATCH 17/19] fix: address CodeRabbit review on the nightly migration - Propagate autocmd option errors instead of defaulting once to false. - Log job wait() failures instead of dropping the OS error. - Document the pinned nightly-2026-08-14 toolchain in the changelog fragment (no floating channel, no cranelift claims). - Install the pinned nightly with its build target in release.yml. - Scope result_large_err to the four websocket functions instead of allowing it workspace-wide. --- .github/workflows/release.yml | 2 +- Cargo.toml | 1 - changelog.d/nightly-toolchain-cranelift.changed.md | 2 +- n00n-lua/src/api/autocmd.rs | 2 +- n00n-lua/src/api/fn.rs | 8 +++++++- n00n-providers/src/providers/openai/platform.rs | 3 ++- n00n-providers/src/providers/openai/websocket.rs | 2 ++ 7 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e4597987e..71f261294 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -106,7 +106,7 @@ jobs: with: persist-credentials: false - name: Install Rust toolchain - run: rustup target add --toolchain nightly-2026-08-14 ${{ matrix.target }} + run: rustup toolchain install nightly-2026-08-14 --profile minimal --target ${{ matrix.target }} - name: Build run: cargo build --release --target ${{ matrix.target }} diff --git a/Cargo.toml b/Cargo.toml index 156797cec..a3d79f21d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -335,7 +335,6 @@ unnecessary_lazy_evaluations = "allow" unnecessary_map_or = "allow" unwrap_or_default = "allow" assert_is_empty = "allow" -result_large_err = "allow" [profile.bench] debug = true diff --git a/changelog.d/nightly-toolchain-cranelift.changed.md b/changelog.d/nightly-toolchain-cranelift.changed.md index a3e7aa656..f4a6e9b16 100644 --- a/changelog.d/nightly-toolchain-cranelift.changed.md +++ b/changelog.d/nightly-toolchain-cranelift.changed.md @@ -1 +1 @@ -n00n now builds on a floating Rust nightly toolchain instead of stable, with the Cranelift codegen backend enabled for dev builds (release builds still use LLVM). CI toolchain steps were switched to nightly, the MSRV job was removed since a floating-nightly policy has no minimum supported version, and the `rust-version` field was dropped from all workspace manifests accordingly. +n00n now builds on the pinned nightly-2026-08-14 Rust toolchain instead of stable. CI toolchain steps were switched to nightly, the MSRV job was removed since the pinned-nightly policy has no minimum supported version, and the `rust-version` field was dropped from all workspace manifests accordingly. \ No newline at end of file diff --git a/n00n-lua/src/api/autocmd.rs b/n00n-lua/src/api/autocmd.rs index f29901b32..8df9293ef 100644 --- a/n00n-lua/src/api/autocmd.rs +++ b/n00n-lua/src/api/autocmd.rs @@ -151,7 +151,7 @@ fn parse_string_or_seq(value: Value, what: &str) -> LuaResult> { fn create_autocmd(lua: &Lua, #[ctx] plugin: Arc, event: Value, opts: Table) -> LuaResult { let events = parse_string_or_seq(event, "event")?; let callback: Function = opts.get("callback")?; - let once: bool = opts.get("once").unwrap_or_else(|_| false); + let once = opts.get::>("once")?.unwrap_or_else(|| false); let patterns = match opts.get::("pattern")? { Value::Nil => None, v => Some(parse_string_or_seq(v, "pattern")?), diff --git a/n00n-lua/src/api/fn.rs b/n00n-lua/src/api/fn.rs index b3a0871c5..84a5c3717 100644 --- a/n00n-lua/src/api/fn.rs +++ b/n00n-lua/src/api/fn.rs @@ -195,7 +195,13 @@ impl JobStore { thread::Builder::new() .name("job-wait".into()) .spawn(move || { - let code = child.wait().map_or(-1, |s| s.code().unwrap_or_else(|| -1)); + let code = match child.wait() { + Ok(status) => status.code().unwrap_or_else(|| -1), + Err(error) => { + tracing::error!(error = %error, "job wait failed"); + -1 + } + }; if let Some(h) = stdout_handle { let _ = h.join(); } diff --git a/n00n-providers/src/providers/openai/platform.rs b/n00n-providers/src/providers/openai/platform.rs index 9dbd41726..33d35da20 100644 --- a/n00n-providers/src/providers/openai/platform.rs +++ b/n00n-providers/src/providers/openai/platform.rs @@ -172,6 +172,7 @@ impl CodexAttempt { } #[derive(Debug)] +#[allow(clippy::result_large_err)] struct OpenAiSessionState { last_response_id: Option, last_message_count: usize, @@ -822,7 +823,7 @@ impl OpenAi { }) } - #[allow(clippy::large_futures)] + #[allow(clippy::large_futures, clippy::result_large_err)] async fn connect_current_websocket( &self, attempt_nonce: u64, diff --git a/n00n-providers/src/providers/openai/websocket.rs b/n00n-providers/src/providers/openai/websocket.rs index a0b5a176b..7fd34c944 100644 --- a/n00n-providers/src/providers/openai/websocket.rs +++ b/n00n-providers/src/providers/openai/websocket.rs @@ -312,6 +312,7 @@ impl ResponsesWebSocket { } } + #[allow(clippy::result_large_err)] pub(crate) async fn stream_message( &mut self, body: &Value, @@ -330,6 +331,7 @@ impl ResponsesWebSocket { } #[allow(clippy::too_many_lines)] + #[allow(clippy::result_large_err)] async fn stream_message_with_keepalive( &mut self, body: &Value, From 29bd01bbf0c3a2a09d9c6d8e29af371584ce6bc9 Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Sat, 15 Aug 2026 02:05:18 -0400 Subject: [PATCH 18/19] fix: scope result_large_err allows to the actual websocket functions --- n00n-providers/src/providers/openai/platform.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/n00n-providers/src/providers/openai/platform.rs b/n00n-providers/src/providers/openai/platform.rs index 33d35da20..9c8b7ec0a 100644 --- a/n00n-providers/src/providers/openai/platform.rs +++ b/n00n-providers/src/providers/openai/platform.rs @@ -172,7 +172,6 @@ impl CodexAttempt { } #[derive(Debug)] -#[allow(clippy::result_large_err)] struct OpenAiSessionState { last_response_id: Option, last_message_count: usize, @@ -876,7 +875,7 @@ impl OpenAi { result } - #[allow(clippy::large_futures)] + #[allow(clippy::large_futures, clippy::result_large_err)] #[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_lines)] async fn stream_websocket( @@ -4036,7 +4035,6 @@ mod tests { #[test] #[allow(clippy::large_futures)] - #[allow(clippy::result_large_err)] fn token_refresh_during_new_socket_handshake_reconnects_before_create() { smol::block_on(async { let listener = smol::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); From e01de2b3239c74ff8582ba052584a366ea907e69 Mon Sep 17 00:00:00 2001 From: w0wl0lxd Date: Sat, 15 Aug 2026 02:14:18 -0400 Subject: [PATCH 19/19] chore(lint): allow result_large_err workspace-wide after all The scoped per-function allows surfaced further sites in closures and test helpers; the deliberately rich WebSocketAttemptError type makes the workspace-wide allow the consistent choice. --- Cargo.toml | 1 + n00n-providers/src/providers/openai/platform.rs | 4 ++-- n00n-providers/src/providers/openai/websocket.rs | 2 -- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a3d79f21d..156797cec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -335,6 +335,7 @@ unnecessary_lazy_evaluations = "allow" unnecessary_map_or = "allow" unwrap_or_default = "allow" assert_is_empty = "allow" +result_large_err = "allow" [profile.bench] debug = true diff --git a/n00n-providers/src/providers/openai/platform.rs b/n00n-providers/src/providers/openai/platform.rs index 9c8b7ec0a..f2827c333 100644 --- a/n00n-providers/src/providers/openai/platform.rs +++ b/n00n-providers/src/providers/openai/platform.rs @@ -822,7 +822,7 @@ impl OpenAi { }) } - #[allow(clippy::large_futures, clippy::result_large_err)] + #[allow(clippy::large_futures)] async fn connect_current_websocket( &self, attempt_nonce: u64, @@ -875,7 +875,7 @@ impl OpenAi { result } - #[allow(clippy::large_futures, clippy::result_large_err)] + #[allow(clippy::large_futures)] #[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_lines)] async fn stream_websocket( diff --git a/n00n-providers/src/providers/openai/websocket.rs b/n00n-providers/src/providers/openai/websocket.rs index 7fd34c944..a0b5a176b 100644 --- a/n00n-providers/src/providers/openai/websocket.rs +++ b/n00n-providers/src/providers/openai/websocket.rs @@ -312,7 +312,6 @@ impl ResponsesWebSocket { } } - #[allow(clippy::result_large_err)] pub(crate) async fn stream_message( &mut self, body: &Value, @@ -331,7 +330,6 @@ impl ResponsesWebSocket { } #[allow(clippy::too_many_lines)] - #[allow(clippy::result_large_err)] async fn stream_message_with_keepalive( &mut self, body: &Value,