Skip to content

fix(daemon): apply config written after daemon startup (first-run race)#8

Open
BluePhi09 wants to merge 1 commit into
Axenide:mainfrom
BluePhi09:fix-first-run-config-race
Open

fix(daemon): apply config written after daemon startup (first-run race)#8
BluePhi09 wants to merge 1 commit into
Axenide:mainfrom
BluePhi09:fix-first-run-config-race

Conversation

@BluePhi09

Copy link
Copy Markdown

Problem

On a fresh home directory (new machine, new user, first login ever), the compositor config that axctl generates (hyprland.conf / hyprland.lua with keybinds, appearance and layer rules) never materializes for the entire first session. Everything only starts working from the second login onward.

Concretely, with Ambxst on Hyprland this means: the shell starts and the bar renders, but none of the Ambxst keybinds (launcher, dashboard, clipboard, …) work and none of the appearance settings (gaps, rounding, layer rules) apply until the user logs out and back in.

Root cause

A startup race between the shell and the daemon:

  1. Ambxst's AxctlService.qml starts axctl daemon immediately (running: true on the Process).
  2. CompositorTomlWriter.qml writes axctl.toml only after that — in Component.onCompleted, gated on the config loader being done, and through a spawned bash process.
  3. runDaemon() in main.go stats the config path once at startup. When the file is missing it printed No config file at …, skipping — and skipped both the initial apply and the creation of the fsnotify watcher.

So on a fresh home the daemon always wins the race, and the config the shell writes moments later is silently ignored until the next session. (The shell never sends Config.Apply over IPC on its own; daemon startup and the file watcher are the only apply paths.)

Fix

  • Always start the config watcher, even when the config file doesn't exist yet. When it's missing, create its parent directory (so it can be watched) and log waiting for it to appear instead of skipping.
  • Watch parent directories in addition to the files themselves. fsnotify cannot watch a path that doesn't exist yet, and file-level watches also break when editors save via atomic rename — the directory watch covers both cases.
  • Filter events by path (isConfigPath) so unrelated files living in the same directory don't trigger reloads. This matters because the daemon itself writes the generated hyprland.conf/hyprland.lua into that very directory (~/.local/share/ambxst/) on every apply — without the filter, directory watching would cause reload feedback loops. Paths are normalized with filepath.Clean on both sides of the comparison.

Testing

Verified end-to-end in a NixOS VM running Hyprland 0.55.4 + Ambxst:

  1. Started the patched daemon with -c pointing into a fresh home where neither the config file nor its directory existed → logs No config file at … yet, waiting for it to appear, directory created, watcher armed.
  2. Wrote an unrelated file into the watched directory → no reload triggered (path filter works).
  3. Wrote a real axctl.toml → picked up immediately: Config changed, reloading…, generated hyprland.conf + hyprland.lua appeared, compositor reload dispatched. Subsequent daemon self-writes of the generated files into the same directory triggered no further reloads.
  4. Regression: starting the daemon with an existing config still applies it at startup (unchanged code path), and go test ./... passes.

🤖 Generated with Claude Code

On a fresh home the shell (Ambxst) starts the axctl daemon immediately but
writes axctl.toml only moments later. The daemon stat'ed the config at
startup and, when missing, skipped BOTH the initial apply AND the fsnotify
watcher — so the first config ever written was silently ignored for the
whole session. Result: on the very first login the generated compositor
config (keybinds, appearance, layer rules) never materialized; everything
started working only from the second login on.

Fix:
- always start the config watcher, even when the file doesn't exist yet
  (and create its parent directory so it can be watched)
- watch parent directories in addition to the files themselves, because
  fsnotify cannot watch a nonexistent path (and file-level watches break
  on atomic rename saves)
- filter events by path so unrelated files in the same directory (e.g. the
  generated hyprland.conf/.lua the daemon itself writes there) don't
  trigger spurious reloads

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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