fix: create the state directory before the auto-spawn path writes into it - #90
Merged
Merged
Conversation
…o it `~/.appduct` was only ever created by `startDaemon`, i.e. inside the daemon child — but the spawn-lock and `daemon.log`'s fd are opened by the *parent* process, before that child exists. On a machine that has never run Appduct, every command that auto-spawns a daemon (`appduct ls`, `appduct daemon start|status`, and `appduct mcp`, which died before an MCP client could finish `initialize`) failed with a bare `ENOENT: ... open '~/.appduct/daemon.spawn.lock'` until someone ran `appduct daemon run` in the foreground once. `spawnDaemonAndWait` now calls `ensureStateDir` before taking the lock, which covers the log fd behind it too; the directory gets the same `0700` the daemon would have applied. Every existing test starts from a `mkdtemp`'d state dir, which is why this went unnoticed — the regression test deliberately points at a path that does not exist, and asserts the directory is there by the time the spawn runs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
On a machine that has never run Appduct, the first command fails:
~/.appductis created bystartDaemon— but that runs inside the daemon child, and the two things the auto-spawn path writes are opened by the parent, before that child exists:acquireSpawnLock→open(paths.spawnLockPath, "wx"), which only swallowsEEXIST;defaultSpawn→open(context.logFilePath, "a")for the detached child's stdio, one line behind it.So every command that auto-spawns hits it:
appduct ls,appduct daemon start|status, and — worst —appduct mcp, which dies before an MCP client can finishinitialize. An agent that adds the MCP server on a clean install gets a hard failure until a human runsappduct daemon runin the foreground once.appduct initdoesn't help: it writes the project.appduct/config.json, never the state dir.Foreground
appduct daemon runwas always fine — it callsensureStateDirfirst.The fix
spawnDaemonAndWaitcallsensureStateDir(paths.root)before taking the lock, which covers the log fd behind it too. The directory gets the same0700the daemon would have applied, andensureStateDiris idempotent, so the existing-dir path is unchanged apart from a mode re-tighten it already does on every daemon start.Why it was never caught
All 64 test files
mkdtemptheir state dir before invoking anything, so the directory always exists. The regression test deliberately points at a path that does not exist and asserts the directory is there by the time the spawn runs (checked inside the injected spawn fn, since that is whatdefaultSpawn's log fd depends on). Without the fix it fails with exactly the reportedENOENTon the lock path.Verification
typecheck,lintandcheck:linksclean.APPDUCT_STATE_DIR: the directory is now created, the daemon is spawned, and it writeskey.pem,audit/anddaemon.log. On this machine the spawned daemon then hitsEADDRINUSEon 8443 because an unrelated daemon already holds the port — with the port free (dir pre-created,wssPort: 0),appduct lsauto-spawns and answers normally.