From 15d44f9b2a89ebac5547e0943d0e6f82548df2d5 Mon Sep 17 00:00:00 2001 From: Bad3r <25513724+Bad3r@users.noreply.github.com> Date: Wed, 6 May 2026 09:56:17 +0300 Subject: [PATCH 1/9] chore: update inputs --- flake.lock | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/flake.lock b/flake.lock index 4dbbcdae4..95ce688d1 100644 --- a/flake.lock +++ b/flake.lock @@ -298,11 +298,11 @@ ] }, "locked": { - "lastModified": 1778008851, - "narHash": "sha256-gn/SN/Kd5f61kBTuObyqdJuGGE+BniurD9UQKsujO2o=", + "lastModified": 1778048802, + "narHash": "sha256-KgXGfVxnUGyIjirpqnc49wK/0Sb3TZ3cPujV3h2eAJk=", "owner": "nix-community", "repo": "NUR", - "rev": "2d9e1961a61896c0672664c39fc7a323362ea175", + "rev": "452c5cea023572b2fe400a512d25b9035f4f15ae", "type": "github" }, "original": { @@ -350,11 +350,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1778004708, - "narHash": "sha256-dZeTLaf9TpkZ1oRT9xQFBPrCAEIDiMRub+RbQAFRqn4=", + "lastModified": 1778035564, + "narHash": "sha256-XJr1aD7alVflwcyqKhl91KbgxeUGCqiWI5cVJ2gb5Jo=", "owner": "nix-community", "repo": "emacs-overlay", - "rev": "a4dca412694e9d1c2b19218ac0e722baecd6103a", + "rev": "522f0c1535cf260f26485ee96b512ba2df057542", "type": "github" }, "original": { @@ -810,11 +810,11 @@ ] }, "locked": { - "lastModified": 1778007384, - "narHash": "sha256-Z+SBVn8dditLJtxi9GoQ5dhyi0dtUf3lgRNhvhhXzU0=", + "lastModified": 1778044692, + "narHash": "sha256-feJrvdnT9SGuhSG7tYWqPf77L3TxmPtk2Xza6HyEIMc=", "owner": "numtide", "repo": "llm-agents.nix", - "rev": "3a926e3686d75aa9d9e3ff52d5e05b0d11cd8b59", + "rev": "6159b9a1a78a736cea2977543e74787c5c382dfa", "type": "github" }, "original": { @@ -868,11 +868,11 @@ ] }, "locked": { - "lastModified": 1777555748, - "narHash": "sha256-RvKwvF868YY6HCfVC3K1kVrlUOnn4ynWxAi5PujhFXo=", + "lastModified": 1778042286, + "narHash": "sha256-FEY/HcDmLa3gFu8eag+hB4kxmZENGHatM/2gG/c/P2Y=", "owner": "natsukium", "repo": "mcp-servers-nix", - "rev": "39253ce656ff86527a060148c40901bbbde6a4c2", + "rev": "59b5a95c2e65abb7821e915c65e3e99607d522f2", "type": "github" }, "original": { @@ -956,11 +956,11 @@ ] }, "locked": { - "lastModified": 1777952747, - "narHash": "sha256-Q7XCl60/tzL9Ki0z1GtgwtyrQiol3xmlD82CndMZKfo=", + "lastModified": 1778040108, + "narHash": "sha256-1BNcfm6jQOoCSm4+xKrQ3HR3z9bOXuzaup30tW4xNq8=", "owner": "Bad3r", "repo": "nix-logseq-git-flake", - "rev": "1eb4086a36c028e0fd8a0c82a50b1c8bb443b227", + "rev": "d6760a143c820673e393e4ac492c6675bc0ef0ef", "type": "github" }, "original": { @@ -1033,11 +1033,11 @@ }, "nixpkgs-stable": { "locked": { - "lastModified": 1777673416, - "narHash": "sha256-5c2POKPOjU40Kh0MirOdScBLG0bu9TAuPYAtPRNZMBs=", + "lastModified": 1778003029, + "narHash": "sha256-q/nkKLDtHIyLjZpKhWk3cSK5IYsFqtMd6UtXF3ddjgA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "26ef669cffa904b6f6832ab57b77892a37c1a671", + "rev": "0c88e1f2bdb93d5999019e99cb0e61e1fe2af4c5", "type": "github" }, "original": { @@ -1049,11 +1049,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1777918403, - "narHash": "sha256-7QiZv0LcW1yIOLo2LNuCQjWon1Z1r99FwK24hbtBOF4=", + "lastModified": 1777946660, + "narHash": "sha256-iw3XDIG6xxk+AZTcawCLHf6i9i4tXRzLZEoV9xhRToQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "afc5551119aae6eab73a95c1960891cfe63204f6", + "rev": "bc57abace07689cfd34203aa5fb4027514895987", "type": "github" }, "original": { From ca83256803ff2f47b451884243846aff9582edcb Mon Sep 17 00:00:00 2001 From: Bad3r <25513724+Bad3r@users.noreply.github.com> Date: Wed, 6 May 2026 10:04:57 +0300 Subject: [PATCH 2/9] feat(build): tee each run to a timestamped log file Stdout and stderr are duplicated into ${XDG_STATE_HOME:-~/.local/state}/nixos-build/build--.log with ANSI codes stripped, so failed runs leave a diagnosable record. The EXIT trap closes the redirected fds and waits for the tee subprocesses so trailing output flushes before the shell returns. The PID suffix prevents collisions when two runs start within the same second. Also swap the bootstrap substituter preference from mirror.sjtu.edu.cn to mirrors.ustc.edu.cn. --- build.sh | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/build.sh b/build.sh index 6086886de..ea84a6617 100755 --- a/build.sh +++ b/build.sh @@ -27,6 +27,8 @@ BOOTSTRAP_CACHES=false ACTION="switch" # default action after build: switch | boot BUILD_FLAGS=() NH_CMD=() +LOG_DIR="${XDG_STATE_HOME:-${HOME}/.local/state}/nixos-build" +LOG_FILE="" # Colors for output (readonly constants) readonly RED='\033[0;31m' readonly GREEN='\033[0;32m' @@ -56,9 +58,12 @@ Options: --bootstrap Use extra substituters for first build (e.g., Determinate Nix) -h, --help Show this help message +Logs: + Each run is recorded to %s/build--.log + Usage Example: ${0} --offline -" "${0##*/}" "${PWD}" "$(hostname)" +" "${0##*/}" "${PWD}" "$(hostname)" "${LOG_DIR}" } # Status messages with printf @@ -89,7 +94,17 @@ trap_error() { } trap trap_error ERR -trap 'true' EXIT # Cleanup hook (no-op; extend as needed) +# Close redirected fds so tee subprocesses see EOF, then wait for them to flush. +trap 'exec >&- 2>&-; wait' EXIT + +setup_logging() { + LOG_FILE="${LOG_DIR}/build-$(date +%Y%m%d-%H%M%S)-$$.log" + mkdir -p "${LOG_DIR}" + exec \ + > >(tee >(sed -u 's/\x1b\[[0-9;]*m//g' >>"${LOG_FILE}")) \ + 2> >(tee >(sed -u 's/\x1b\[[0-9;]*m//g' >>"${LOG_FILE}") >&2) + status_msg "${GREEN}" "Logging to: ${LOG_FILE}" +} # Parse command-line arguments while [[ $# -gt 0 ]]; do @@ -201,8 +216,8 @@ fi # Used for initial Determinate Nix setup or similar migrations BOOTSTRAP_SUBSTITUTERS=( "https://mirrors.tuna.tsinghua.edu.cn/nix-channels/store" - "https://mirror.sjtu.edu.cn/nix-channels/store" - #"https://mirrors.ustc.edu.cn/nix-channels/store" + #"https://mirror.sjtu.edu.cn/nix-channels/store" + "https://mirrors.ustc.edu.cn/nix-channels/store" "https://cache.nixos.org" "https://cache.garnix.io?priority=38" "https://cache.numtide.com" @@ -423,6 +438,7 @@ run_firmware_updates() { } main() { + setup_logging configure_nix_config configure_build_flags From 034ca19a7ced6d5be7b76a78d4002f502c9b8ba9 Mon Sep 17 00:00:00 2001 From: Bad3r <25513724+Bad3r@users.noreply.github.com> Date: Wed, 6 May 2026 10:36:03 +0300 Subject: [PATCH 3/9] docs(custom-packages): correct overlay registration and validation guidance The Registering in the Overlay section pointed to modules/system76/custom-packages-overlay.nix, but in-tree custom packages live in the shared customPackages overlay at modules/base/custom-packages-overlay.nix and hosts opt in via nixpkgs.overlays. The per-host files now carry only host-specific patches. Hash-fetching workflow now uses nix build .#nixosConfigurations..pkgs., since overlay-backed packages are not exposed as top-level flake outputs and the previous nix build .# invocation always failed. When to Create an App Module now lists host-visible tooling as an explicit reason to add an app module (sss-pass-gpg-bootstrap and sss-nix-repair are both wired this way), and documents the apps-catalog-sync pre-commit hook contract that requires every app module to have a matching entry in each host's apps-enable.nix. Validation checklist updated to cite the correct registration file, the host-aware build command, and the apps-catalog-sync enforcement. --- docs/guides/custom-packages-style-guide.md | 35 ++++++++++++---------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/guides/custom-packages-style-guide.md b/docs/guides/custom-packages-style-guide.md index 253be15e6..84163b143 100644 --- a/docs/guides/custom-packages-style-guide.md +++ b/docs/guides/custom-packages-style-guide.md @@ -259,22 +259,23 @@ Optional but recommended: ## Registering in the Overlay -After creating the package, register it in `modules/system76/custom-packages-overlay.nix`: +In-tree custom packages are exposed through the shared `customPackages` overlay at `modules/base/custom-packages-overlay.nix`. Add a single entry there for each new package: ```nix _: { - configurations.nixos.system76.module = { - nixpkgs.overlays = [ - (final: _prev: { - # Existing packages... - my-new-package = final.callPackage ../../packages/my-new-package { }; - }) - ]; + flake.lib.overlays.customPackages = final: prev: { + # Existing packages... + my-new-package = final.callPackage ../../packages/my-new-package { }; }; } ``` -This makes the package available as `pkgs.my-new-package` throughout the configuration. +Hosts opt in by composing the shared overlay into `nixpkgs.overlays`. The wiring already exists for both managed hosts: + +- `modules/system76/custom-packages-overlay.nix` composes the shared overlay and layers host-specific patches on top (e.g., the `system76-power` patch). +- `modules/tpnix/custom-packages-overlay.nix` composes the shared overlay alone. + +Use the per-host files only for host-only patches, version overrides, or hardware-specific tweaks; new in-tree packages belong in the shared overlay so every host that opts in sees them. After registration the package is available as `pkgs.my-new-package` on every host whose `custom-packages-overlay` module is loaded. ## Hash Fetching Workflow @@ -286,12 +287,14 @@ This makes the package available as `pkgs.my-new-package` throughout the configu hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; ``` -2. Attempt to build: +2. Attempt to build the package through the overlay-aware host `pkgs`: ```bash - nix build .#my-package + nix build .#nixosConfigurations.system76.pkgs.my-package ``` + Overlay-backed packages are not exposed as top-level flake outputs, so `nix build .#my-package` will fail with `does not provide attribute packages.x86_64-linux.my-package`. Always go through `nixosConfigurations..pkgs.` to apply the overlay. + 3. Copy the `got:` hash from the error message into your file. Alternatively, use `nix-prefetch-github`: @@ -352,13 +355,15 @@ Create a corresponding app module in `modules/apps/.nix` when: - The package is a user-facing application - Users should be able to enable/disable it declaratively - The package needs unfree allowlisting or extra configuration +- The package is host-visible tooling such as a shell helper or system-management script (e.g., `modules/apps/sss-pass-gpg-bootstrap.nix`, `modules/apps/sss-nix-repair.nix`) Skip the app module when: -- The package is host-specific tooling - The package is only used in devshells - The package is a library or build tool +The `apps-catalog-sync` pre-commit hook (`modules/meta/hooks/apps-catalog-sync.nix`) enforces that every app module under `modules/apps/` has a matching entry in each host's `apps-enable.nix`. After adding the app module, register it in `modules/system76/apps-enable.nix` and `modules/tpnix/apps-enable.nix`. Use `lib.mkOverride 1100 false` as the default and flip individual host catalogs to `true` only where the tool should be installed. + See [Apps Module Style Guide](apps-module-style-guide.md) for the app module format. ## Validation Checklist @@ -367,10 +372,10 @@ Before committing a new package: - [ ] File exists at `packages//default.nix` - [ ] All required meta fields are present -- [ ] Package is registered in `custom-packages-overlay.nix` -- [ ] `nix build .#` succeeds (or via overlay: test in config) +- [ ] Package is registered in `modules/base/custom-packages-overlay.nix` (shared `customPackages` overlay) +- [ ] `nix build .#nixosConfigurations..pkgs.` succeeds (overlay-backed packages are not exposed as top-level flake outputs) - [ ] `nix flake check --accept-flake-config` passes -- [ ] App module created if user-facing (see [Apps Module Style Guide](apps-module-style-guide.md)) +- [ ] App module created if user-facing or host-visible (see [Apps Module Style Guide](apps-module-style-guide.md)); `apps-catalog-sync` will fail the push if `modules/apps/.nix` lacks a matching entry in every host's `apps-enable.nix` ## Reference Implementations From c8d926a9539de7605647342df1a274e3747f454c Mon Sep 17 00:00:00 2001 From: Bad3r <25513724+Bad3r@users.noreply.github.com> Date: Wed, 6 May 2026 10:36:10 +0300 Subject: [PATCH 4/9] chore: drop notes.md scratch file --- notes.md | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 notes.md diff --git a/notes.md b/notes.md deleted file mode 100644 index af1077e77..000000000 --- a/notes.md +++ /dev/null @@ -1,11 +0,0 @@ -## TODO - -1. High: the overlay registration section is stale and host-specific. custom-packages-style-guide.md:262 and custom-packages-style-guide.md:277 still tell authors to register custom packages only in modules/system76/custom-packages-overlay.nix and - imply that makes them globally available. Current repo practice is per-host overlay wiring, as shown in custom-packages-overlay.nix:19 and custom-packages-overlay.nix:18. -2. High: the validation command is wrong for overlay-backed custom packages. custom-packages-style-guide.md:292 and custom-packages-style-guide.md:371 recommend nix build .#, but this repo does not expose these packages as top-level flake - package outputs. I verified that nix develop -c nix build .#sss-pass-gpg-bootstrap fails with “does not provide attribute packages.x86_64-linux.sss-pass-gpg-bootstrap”. The working validation path is via host configs or overlay consumers. -3. Medium: the app-module guidance under-specifies the host catalog contract. custom-packages-style-guide.md:358 says to skip app modules for host-specific tooling, and custom-packages-style-guide.md:373 stops at “App module created”. In practice, - host-visible tooling is still modeled as app modules plus explicit host catalog entries, which the push hook enforces. Current evidence: sss-pass-gpg-bootstrap.nix:1, apps-enable.nix:303, apps-enable.nix:280, and the existing host-scoped tooling - pattern in sss-nix-repair.nix:21. - -- burpsuitepro.desktop> /nix/store/a14ldmhl5wsmz4mh9ybbvkvjjbxmkigx-burpsuitepro.desktop/share/applications/burpsuitepro.desktop: hint: value "Development;Security;System" for key "Categories" in group "Desktop Entry" contains more than one main category; application might appear more than once in the application menu From afea165ced9dfa26e43e38d9536e0a7124d6b6dd Mon Sep 17 00:00:00 2001 From: Bad3r <25513724+Bad3r@users.noreply.github.com> Date: Wed, 6 May 2026 10:45:38 +0300 Subject: [PATCH 5/9] feat(rip): ship hand-written zsh completion alongside wrapper Ship a _rip completion file with the rip wrapper so zsh's compinit indexes a stable definition tied to the wrapper's actual flag set. Eliminates the stale-cache shadowing window where a previously installed rip2 _rip lingered in ~/.cache/zsh/zcompdump after the package left the closure. The completion mirrors the wrapper's mode_count check: --empty-trash, -s/--seance, -u/--unbury, and positional FILES are mutually exclusive, while --graveyard and --force are universal modifiers. -i/--inspect is gated to FILES mode. -r/-R/-d are exposed with an "rm compat, no-op" description for muscle-memory invocations like 'rip -rfd path/'. Verification: - nix build .#nixosConfigurations.system76.config.system.build.toplevel - find -L result/sw/share/zsh/site-functions -name _rip resolves to the rip-zsh-completion derivation output - zsh -n parses the script cleanly; compinit autoloads _rip from the shipped path and registers _comps[rip]=_rip --- modules/apps/rip.nix | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/modules/apps/rip.nix b/modules/apps/rip.nix index a8019e6c2..53ad24846 100644 --- a/modules/apps/rip.nix +++ b/modules/apps/rip.nix @@ -245,6 +245,45 @@ let trash-put "''${TRASH_PUT_ARGS[@]}" "''${TRASH_DIR_ARGS[@]}" -- "''${FILES[@]}" ''; }; + + ripCompletion = pkgs.writeTextFile { + name = "rip-zsh-completion"; + destination = "/share/zsh/site-functions/_rip"; + text = '' + #compdef rip + + # Hand-written completion for the rip wrapper. Source of truth lives + # alongside the wrapper in this same module; the flag list must stay + # in sync with the case-statements in ripWrapper above. + + _rip_files() { + # Mirror the escaping shim trash-cli uses in _trash_files (see + # _trash-put / _trash-restore on the same fpath). Without this, + # _files glob-expands metacharacters in already-typed args. + (( CURRENT > 0 )) && line[CURRENT]=() + line=( ''${line//(#m)[\[\]()\\*?#<>~\^\|]/\\$MATCH} ) + _files -F line + } + + _rip() { + local context state line curcontext="$curcontext" + typeset -A opt_args + + _arguments -C -s -S \ + '(- *)'{-h,--help}'[print help and exit]' \ + '(--empty-trash --seance -s --unbury -u *)--empty-trash[permanently empty the trash]' \ + '(--empty-trash --seance -s --unbury -u *)'{-s,--seance}'[list files trashed from cwd]' \ + '(--empty-trash --seance -s --unbury -u)'{-u,--unbury}'[restore trashed files (interactive picker if no PATH)]:*::path to restore:_rip_files' \ + '(--empty-trash --seance -s --unbury -u)'{-i,--inspect}'[inspect (ls -la) and prompt before trashing]' \ + {-f,--force}'[skip all confirmation prompts]' \ + '--graveyard=[override trash directory for this invocation]:trash dir:_files -/' \ + {-r,-R,-d}'[rm compat, no-op]' \ + '*:files to trash:_rip_files' + } + + _rip "$@" + ''; + }; in { options.programs.rip.extended = { @@ -290,6 +329,7 @@ let environment.systemPackages = [ cfg.package ripWrapper + ripCompletion ]; systemd.tmpfiles.rules = [ From cbd951cc376415c6d05eafb7d62b298bf9f9fe80 Mon Sep 17 00:00:00 2001 From: Bad3r <25513724+Bad3r@users.noreply.github.com> Date: Wed, 6 May 2026 11:13:08 +0300 Subject: [PATCH 6/9] fix(rip): harden _rip_files for extendedglob and words context Force `extendedglob` locally so the `(#m)` glob qualifier and `$MATCH` substitution inside the metachar escape pass keep working when the caller's shell disables the option, and switch the working buffer from `line` to a copy of `words`. `line` only holds the positional args parsed so far; `CURRENT` is the index of the active token in `words`, so `line[CURRENT]=()` was a silent no-op and the in-progress half-typed path stayed in the exclusion list. Operating on `tmp_words` makes `[CURRENT]=()` actually remove the active word, and the dead `(( CURRENT > 0 ))` guard goes with it (CURRENT is always >= 1 inside a completion function). --- modules/apps/rip.nix | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/modules/apps/rip.nix b/modules/apps/rip.nix index 53ad24846..6901c2949 100644 --- a/modules/apps/rip.nix +++ b/modules/apps/rip.nix @@ -257,12 +257,21 @@ let # in sync with the case-statements in ripWrapper above. _rip_files() { + # `(#m)` and `$MATCH` require extendedglob; ensure it is set even + # when the caller's shell disables it. `localoptions` confines the + # change to this function. + setopt localoptions extendedglob # Mirror the escaping shim trash-cli uses in _trash_files (see - # _trash-put / _trash-restore on the same fpath). Without this, - # _files glob-expands metacharacters in already-typed args. - (( CURRENT > 0 )) && line[CURRENT]=() - line=( ''${line//(#m)[\[\]()\\*?#<>~\^\|]/\\$MATCH} ) - _files -F line + # _trash-put / _trash-restore on the same fpath). Operate on a + # copy of `words` because `line` only holds positional args while + # `CURRENT` indexes `words`; mixing the two leaves the in-progress + # word in the exclusion set and silently breaks completion when a + # half-typed path contains a metachar. + local -a tmp_words + tmp_words=( "''${words[@]}" ) + tmp_words[CURRENT]=() + tmp_words=( ''${tmp_words//(#m)[\[\]()\\*?#<>~\^\|]/\\$MATCH} ) + _files -F tmp_words } _rip() { From 7b2c5f915b8fe6a56af31dab767be5c17405293b Mon Sep 17 00:00:00 2001 From: Bad3r <25513724+Bad3r@users.noreply.github.com> Date: Wed, 6 May 2026 11:13:35 +0300 Subject: [PATCH 7/9] fix(rip): tighten _arguments exclusivity and --graveyard handling Drop the trailing `=` from `--graveyard` so `_arguments` matches both `--graveyard PATH` and `--graveyard=PATH`. The wrapper accepts both forms; the previous spec only completed for the equals form, leaving `--graveyard ` with no directory action. Add `-i --inspect` to the exclusion lists of the three other mode flags so the symmetry of the wrapper's `mode_count` rejection is visible in completion: `-i` is a trash-mode modifier and is no-op (or worse, misleading) when paired with `--empty-trash`, `-s/--seance`, or `-u/--unbury`. Add `*` to `-u/--unbury`'s exclusion list so the global `*:files to trash:_rip_files` spec stops firing once `-u` has been typed; the local `:*::path to restore:_rip_files` spec already provides positional completion for the restore mode and the wrapper forbids mixing restore targets with files to trash. --- modules/apps/rip.nix | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/modules/apps/rip.nix b/modules/apps/rip.nix index 6901c2949..3989e12e2 100644 --- a/modules/apps/rip.nix +++ b/modules/apps/rip.nix @@ -278,14 +278,21 @@ let local context state line curcontext="$curcontext" typeset -A opt_args + # Exclusion lists mirror the wrapper's mode_count check: the four + # mode flags (--empty-trash, -s/--seance, -u/--unbury, positional + # FILES) are mutually exclusive. -i/--inspect is a trash-mode + # modifier and is rejected by the wrapper alongside the mode + # flags, so list it symmetrically. -u/--unbury also excludes the + # global positional spec (`*`) so files-to-trash completion stops + # firing once the operator has committed to a restore. _arguments -C -s -S \ '(- *)'{-h,--help}'[print help and exit]' \ - '(--empty-trash --seance -s --unbury -u *)--empty-trash[permanently empty the trash]' \ - '(--empty-trash --seance -s --unbury -u *)'{-s,--seance}'[list files trashed from cwd]' \ - '(--empty-trash --seance -s --unbury -u)'{-u,--unbury}'[restore trashed files (interactive picker if no PATH)]:*::path to restore:_rip_files' \ + '(--empty-trash --seance -s --unbury -u -i --inspect *)--empty-trash[permanently empty the trash]' \ + '(--empty-trash --seance -s --unbury -u -i --inspect *)'{-s,--seance}'[list files trashed from cwd]' \ + '(--empty-trash --seance -s --unbury -u -i --inspect *)'{-u,--unbury}'[restore trashed files (interactive picker if no PATH)]:*::path to restore:_rip_files' \ '(--empty-trash --seance -s --unbury -u)'{-i,--inspect}'[inspect (ls -la) and prompt before trashing]' \ {-f,--force}'[skip all confirmation prompts]' \ - '--graveyard=[override trash directory for this invocation]:trash dir:_files -/' \ + '--graveyard[override trash directory for this invocation]:trash dir:_files -/' \ {-r,-R,-d}'[rm compat, no-op]' \ '*:files to trash:_rip_files' } From ef91f0fee19c0612e6fb5597cdbf3e7c732e623b Mon Sep 17 00:00:00 2001 From: Bad3r <25513724+Bad3r@users.noreply.github.com> Date: Wed, 6 May 2026 11:13:48 +0300 Subject: [PATCH 8/9] fix(build): merge stdout and stderr into a single tee for the run log Two `sed -u | tee` chains were appending to ${LOG_FILE} concurrently, one per fd. POSIX guarantees atomic O_APPEND writes only up to PIPE_BUF (4096 bytes on Linux); a verbose nix build can emit longer single lines (long store-path arrays, derivation dumps) that interleave at byte boundaries between the two appenders. Redirect stderr into stdout before the tee so a single sed process owns the log writer. The outer tee still preserves ANSI on the terminal; only the file copy goes through sed. Stream distinction in the log was already absent, so nothing of value is lost; the trade-off is that terminal output now lands on a single fd, which keeps colored status lines but loses the stdout/stderr split that previously held on the controlling terminal. --- build.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/build.sh b/build.sh index ea84a6617..a4a8ce85f 100755 --- a/build.sh +++ b/build.sh @@ -100,9 +100,13 @@ trap 'exec >&- 2>&-; wait' EXIT setup_logging() { LOG_FILE="${LOG_DIR}/build-$(date +%Y%m%d-%H%M%S)-$$.log" mkdir -p "${LOG_DIR}" - exec \ - > >(tee >(sed -u 's/\x1b\[[0-9;]*m//g' >>"${LOG_FILE}")) \ - 2> >(tee >(sed -u 's/\x1b\[[0-9;]*m//g' >>"${LOG_FILE}") >&2) + # Merge stderr into stdout before the tee so a single sed process + # appends to the log. POSIX guarantees atomic O_APPEND only up to + # PIPE_BUF (4096 bytes on Linux); separate stdout/stderr appenders + # would interleave at byte boundaries when verbose nix output emits + # long single lines (store-path arrays, dumped derivations). The + # outer tee preserves ANSI on the terminal; sed strips the file copy. + exec > >(tee >(sed -u 's/\x1b\[[0-9;]*m//g' >>"${LOG_FILE}")) 2>&1 status_msg "${GREEN}" "Logging to: ${LOG_FILE}" } From 6205d6ab73afcd724158c2f09f32db68f05cdbea Mon Sep 17 00:00:00 2001 From: Bad3r <25513724+Bad3r@users.noreply.github.com> Date: Wed, 6 May 2026 11:49:05 +0300 Subject: [PATCH 9/9] fix(rip): restore `--graveyard=` spec for both call forms Reverting the trailing-`=` removal from `7b2c5f91`. Empirical PTY test against the rebuilt completion confirmed claude's nuance was correct: Spec form | `--graveyard PATH` | `--graveyard=PATH` --- | --- | --- `--graveyard=` | completes (`dir`) | completes (`=dir`) `--graveyard` | completes (`dir`) | does NOT complete Per `zshcompsys(1)` ("`-optname=`: argument may appear as the next word, or in same word as the option name provided that it is separated from it by an equals sign"), the trailing `=` in the spec is what enables both call forms; without it, the same-word `--graveyard=PATH` form silently fails to complete. The wrapper accepts both forms (modules/apps/rip.nix:125-135), so the completion needs to do the same. The original spec was already correct; gemini's claim that the `=` form excluded the space form was inverted from how the spec parser actually behaves. --- modules/apps/rip.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/apps/rip.nix b/modules/apps/rip.nix index 3989e12e2..7f2b3840f 100644 --- a/modules/apps/rip.nix +++ b/modules/apps/rip.nix @@ -292,7 +292,7 @@ let '(--empty-trash --seance -s --unbury -u -i --inspect *)'{-u,--unbury}'[restore trashed files (interactive picker if no PATH)]:*::path to restore:_rip_files' \ '(--empty-trash --seance -s --unbury -u)'{-i,--inspect}'[inspect (ls -la) and prompt before trashing]' \ {-f,--force}'[skip all confirmation prompts]' \ - '--graveyard[override trash directory for this invocation]:trash dir:_files -/' \ + '--graveyard=[override trash directory for this invocation]:trash dir:_files -/' \ {-r,-R,-d}'[rm compat, no-op]' \ '*:files to trash:_rip_files' }