Skip to content

fix: declutter writable activation artifacts before nix linkGeneration - #2323

Merged
shunkakinoki merged 1 commit into
mainfrom
feat/declarative-cleanup-hook
Aug 9, 2026
Merged

fix: declutter writable activation artifacts before nix linkGeneration#2323
shunkakinoki merged 1 commit into
mainfrom
feat/declarative-cleanup-hook

Conversation

@shunkakinoki

@shunkakinoki shunkakinoki commented Aug 9, 2026

Copy link
Copy Markdown
Owner

What

Two pre-linkGeneration home-manager activation hooks that remove writable files left by Hermes activate.sh and Cass hydrate.sh before the next switch expects clean symlinks.

Why

  • Hermes activate.sh replaces Nix-managed systemd unit symlinks with writable copies so Hermes can auto-refresh them
  • Cass hydrate.sh writes sources.toml to ~/.config/cass/
  • Both happen after writeBoundary, so on the next nix-switch, home-manager's linkGeneration finds regular files instead of symlinks and refuses to clobber them

Files changed

  • home-manager/services/hermes/default.nix — cleanup before linkGeneration for hermes-gateway.service, hermes-dashboard.service, hermes-dashboard-proxy.service
  • home-manager/programs/cass/default.nix — cleanup before linkGeneration for sources.toml

Summary by cubic

Clean up writable activation artifacts before home-manager linkGeneration so the next nix-switch can safely recreate symlinks and avoid clobber errors. Adds pre-linkGeneration hooks for Hermes and Cass.

  • Bug Fixes
    • Hermes: delete non-symlink user units before linkGeneration (hermes-gateway.service, hermes-dashboard.service, hermes-dashboard-proxy.service).
    • Cass: delete ~/.config/cass/sources.toml before linkGeneration.

Written for commit b3ce01f. Summary will update on new commits.

Review in cubic

Hermes activate.sh replaces Nix-managed systemd unit symlinks with writable
copies so Hermes can auto-refresh them. Cass hydrate.sh writes sources.toml.
Both happen after writeBoundary, so they block the next nix-switch which
expects only symlinks in those locations.

Add pre-linkGeneration cleanup hooks that remove any non-symlink leftovers
before home-manager's linkGeneration phase runs.
@indent-zero

indent-zero Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Warning

Indent Zero is shutting down on August 7th. Please migrate over to Indent 2.0 to continue getting PR reviews.

PR Summary

Adds pre-linkGeneration cleanup hooks to two home-manager modules whose post-writeBoundary scripts leave writable files where the next nix-switch expects symlinks, unblocking subsequent activations without changing steady-state behavior.

  • home-manager/programs/cass/default.nix: new cassSourcesCleanup activation entry (entryBefore [ "linkGeneration" ]) that removes ~/.config/cass/sources.toml so hydrate.sh's regenerated file doesn't block the next switch.
  • home-manager/services/hermes/default.nix: new hermesCleanup activation entry (gated by host.isKyber, entryBefore [ "linkGeneration" ]) that removes non-symlink copies of hermes-gateway.service, hermes-dashboard.service, and hermes-dashboard-proxy.service created by activate.sh, guarded by [ -f && ! -L ] so valid store symlinks are preserved.

Issues

2 potential issues found:

  • cassSourcesCleanup only removes the Linux XDG path (~/.config/cass/sources.toml), but hydrate.sh writes ~/Library/Application Support/cass/sources.toml on Darwin and the module is imported unconditionally — the cleanup is a no-op on macOS and won't help if the same collision ever occurs there. → Autofix
  • Cleanup entries use entryBefore [ "linkGeneration" ] only, so the DAG may schedule them before writeBoundary/checkLinkTargets; today this works because activate.sh writes byte-identical content, but the first time a systemd unit definition changes, checkLinkTargets will cmp-fail and abort activation before the cleanup ever runs. Pin them with config.lib.dag.entryBetween [ "linkGeneration" ] [ "writeBoundary" ]. → Autofix

CI Checks

Waiting for CI checks...


⚡ Autofix All Issues

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 77d9b536-d83e-4e01-b3bd-c8bc10eb37dc

📥 Commits

Reviewing files that changed from the base of the PR and between 950f8dc and b3ce01f.

📒 Files selected for processing (2)
  • home-manager/programs/cass/default.nix
  • home-manager/services/hermes/default.nix

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Removed stale configuration artifacts before applying updated settings, preventing outdated data from persisting across configuration switches.
    • Cleaned up obsolete service files during activation while preserving managed links.
    • Added safe dry-run support for service cleanup operations.

Walkthrough

Home Manager now cleans stale Cass and Hermes activation artifacts before linkGeneration. Cass removes sources.toml. Hermes removes regular unit files while preserving symlinks. Both activations support dry-run execution.

Changes

Activation cleanup

Layer / File(s) Summary
Pre-linkGeneration artifact cleanup
home-manager/programs/cass/default.nix, home-manager/services/hermes/default.nix
Cass removes .config/cass/sources.toml when present. Hermes removes existing regular service unit files and preserves symlinks. Both cleanup steps run before linkGeneration through $DRY_RUN_CMD.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Poem

A rabbit hops before the link,
Clears stale files in just a blink.
Cass leaves clean sources in its place,
Hermes keeps each symlink’s grace.
Dry runs guide the careful way.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/declarative-cleanup-hook

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@shunkakinoki
shunkakinoki merged commit 47d5335 into main Aug 9, 2026
20 of 29 checks passed
@shunkakinoki
shunkakinoki deleted the feat/declarative-cleanup-hook branch August 9, 2026 14:50
lib.mkIf host.isKyber {
# Clean up writable copies that Hermes activate.sh creates (replacing Nix-managed
# symlinks) so they don't block the next nix-switch's linkGeneration.
home.activation.hermesCleanup = config.lib.dag.entryBefore [ "linkGeneration" ] ''

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cleanup can be scheduled before writeBoundary/checkLinkTargets.

lib.hm.dag.entryBefore x is defined as entryBetween x [ ] (see modules/lib/dag.nix), so there is no constraint tying this entry to run after writeBoundary. Home-manager's own convention (home.activation docs) is: "If the script block produces any observable side effect … it must be placed after the special writeBoundary script block."

Why this matters here: checkLinkTargets runs entryBefore [ "writeBoundary" ] and only tolerates an existing non-symlink at a target path when cmp -s finds the content identical to the store version. activate.sh currently writes back the byte-identical content via printf '%s\n' "$content", so checkLinkTargets happens to pass today. The first time any of the three hermes units gets a Nix-side change (Environment=, ExecStart=, etc.), the writable copy on disk will differ from the new store version, checkLinkTargets will abort activation — and if the DAG has scheduled this cleanup after checkLinkTargets, the cleanup never runs and the switch is blocked exactly as this PR is trying to prevent.

Suggested fix (also applies to cassSourcesCleanup):

home.activation.hermesCleanup = config.lib.dag.entryBetween [ "linkGeneration" ] [ "writeBoundary" ] ''
  ...
'';
Suggested change
home.activation.hermesCleanup = config.lib.dag.entryBefore [ "linkGeneration" ] ''
home.activation.hermesCleanup = config.lib.dag.entryBetween [ "linkGeneration" ] [ "writeBoundary" ] ''

# block the next nix-switch.
home.activation.cassSourcesCleanup = config.lib.dag.entryBefore [ "linkGeneration" ] ''
$DRY_RUN_CMD ${pkgs.bash}/bin/bash -c '
if [ -f "${config.home.homeDirectory}/.config/cass/sources.toml" ]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Darwin path missed. hydrate.sh:37-41 selects ~/Library/Application Support/cass/sources.toml on Darwin and ~/.config/cass/sources.toml elsewhere, and this module is imported unconditionally (home-manager/programs/default.nix:11). The cleanup hard-codes the Linux path, so on macOS it never fires. Today this is defensive-only (sources.toml isn't declared in home.file anywhere I could find), but it does mean the migration case the hydrate.sh comment calls out ("Older generations symlinked this file into the Nix store; replace it.") is only handled on Linux.

Consider selecting the path from Nix, e.g.:

let
  sourcesFile =
    if pkgs.stdenv.isDarwin then
      "${config.home.homeDirectory}/Library/Application Support/cass/sources.toml"
    else
      "${config.home.homeDirectory}/.config/cass/sources.toml";
in

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant