Use when changing daemon startup, singleton ownership, shutdown, logging, event subscriptions, or lifecycle commands.
66
80%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Daemon Singleton Lock (internal/daemon/lock.go)
NM_HOME: an exclusive OS file lock on <NM_HOME>/daemon.lock is acquired as the very first action in RunWithOptions, strictly before stale-run recovery and socket bind, and held for the process lifetime. The kernel releases it on any process death, so a held lock always means a live holder and no staleness heuristic is needed. Without it, a second daemon stole the socket and ran global crash recovery against the live daemon's runs and worktrees.TestStartDetachedDaemonDetectsChildExitPromptly, TestStartDetachedDaemonTimeoutKillsAndReapsChild, TestStartPreservesManagedAndDetachedFallbackErrors, TestColdDetachedStartupProductionGateCardinality.waitForDaemonStop and stopDetachedDaemon; regressions: e2e TestDaemonStopLeavesNoDaemonProcessOwningTheRoot, TestDaemonRestartReplacesTheDaemonWithExactlyOneOwner.internal/ipc listen() dials the socket before unlinking it and refuses to steal a live one; client probes bound the dial with daemon_connect_timeout and fail fast on a dead or wedged socket instead of starting a replacement daemon (EnsureDaemon surfaces the error with a daemon start recovery hint; the health RPC itself is bounded separately by ipc.DefaultDialTimeout).no-mistakes daemon run --root); never let inherited environment reinterpret probes like --version or status as daemon workers.pending or running; startRun inserts the run row before creating the worktree, so a no-row directory is safe to remove immediately. That no-row rule holds only inside <NM_HOME>/worktrees, which is discovered by walking because no-mistakes owns it; a configured worktree root is the operator's directory, so cleanup and eject there act on exactly the recorded run worktrees and never enumerate anything else.docs/src/content/docs/concepts/daemon.md; the lock rationale lives in the internal/daemon/lock.go and daemon.go comments. Regressions: TestAcquireSingletonLock_*, TestRunWithResources_SecondDaemonForSameRootFailsWithoutStealingSocket, TestRunWithOptions_RequiresSingletonLockBeforeRecovery, TestRecoverOnStartup_DoesNotDeleteActiveRunWorktree, TestServe_SecondListenerForLiveSocketDoesNotStealIt, TestDialConnectTimeoutFailsFastAndNamesSocket, TestIsRunningFailsFastWhenSocketAcceptsButDoesNotRespond, TestIsRunningSurfacesExistingDeadSocket, TestDaemonRunRootFromArgs_EnvDoesNotForceDaemonModeForProbes, TestValidateDaemonPIDFallback_RefusesToKillOwnProcess.Bounded Daemon Logging and Event-Driven AXI Runs
internal/logstore owns all daemon-process byte and retention bounds. Lifecycle output uses logs/daemon.log, managed Rovo Dev/OpenCode stdout and stderr use logs/managed-server.log, and service bootstrap/direct crash output uses logs/daemon-bootstrap.log. Rotation snapshots backups and truncates the current inode in place so held service and child descriptors keep writing to the bounded current file. Regressions: internal/logstore/rotate_test.go, TestDetachedDaemonUsesBoundedDedicatedLogSinks, TestManagedServerOutputIsSeparatedFromLifecycleFailureSummary.internal/cli/run_reconciler.go is the sole owner of event reconciliation, reconnect, duplicate-event coalescing, and the slow lost-event heartbeat. Do not reintroduce fixed-interval get_run polling. A pre-drive get_active_run or get_run state read that misses its per-attempt deadline is a slow reply, not a dead daemon: classify the timeout, health-probe, and retry. axi run/axi respond default --wait 8m independently so the hold cannot sit on a 10-minute harness cap, and subscription acknowledgement must honor that context. Regressions: TestSuccessfulReadRequestsDoNotLogAtInfo, TestRequestLoggingKeepsMutationsAndFailuresVisible, TestDriveRun_HealthyWaitStaysWithinRequestBudget, TestDriveRun_SlowGetRunRetriesAfterHealthProbe, TestAxiRun_SlowActiveRunReadRetriesInsteadOfStartingAnotherRun, TestAxiRespond_SlowInitialRunReadRetries, TestAxiRun_WaitInterruptsSubscriptionAcknowledgement, TestAxiRun_WaitElapsedAgainstLiveIdleDaemon, TestRunReconciler_*.Bounded Loss-Aware Event Subscriptions
internal/ipc/events.go (ClassOf) is the single event taxonomy: activity is droppable, state is not, control is broker-generated, and an unrecognized type fails safe to state. Brokers and consumers must read loss tolerance from it rather than re-listing event names.internal/daemon/eventmailbox.go is the single overflow owner: a per-subscriber ring bounded by 64 events and 1 MiB, non-blocking publish (the executor is never stalled), activity as the only evictable class, and everything else folded into one sticky coalescing stream_gap that drains ahead of queued payload. A reserved slot is not enough - it fails at the second simultaneous transition - and producer-side channel receives race the reader, which is why the queue is a ring under a mutex.get_run snapshot carries a monotonic StateRev; runSnapshot samples it before the DB read, which is sound only because every producer writes state and then emits. Consumers apply a delta only when its revision is newer, so a delta queued before a snapshot cannot regress state after it. Every subscription opens gapped, so attach and reconnect always reconcile first.ipc.MethodGetStepDiff (RunManager.StepDiff, bounded at 512 KiB) instead of riding the stream: it was the only unbounded payload, and one frame past the 1 MiB transport line limit ends the subscription and hides every later event.internal/daemon/eventmailbox_test.go (A1-A13 plus the byte/count ceilings), TestRunSnapshot_*, TestStepDiff_*, TestExecutor_StateEventsAreEmittedAfterTheirDatabaseWrite, TestClassOfUnknownEventFailsSafeToState, TestRunReconciler_StreamGapForcesOneAuthoritativeRead, TestSubscribeOversizedFrameEndsTheStreamAndHidesLaterEvents, internal/tui/overflow_contract_test.go.Destructive Daemon Lifecycle Guard (internal/lifecycle/guard.go)
daemon stop, daemon restart, and update refuse by default while pending/running runs exist (the daemon is machine-wide, so stopping it can fail every active pipeline), list the runs via the shared lifecycle.ActiveRuns/lifecycle.RunList helpers, and require an explicit --force. update -y answers only the different-executable prompt and deliberately does not bypass this guard.logLifecycleInvocation to <NM_HOME>/logs/cli.log; this is the incident forensic trail, do not remove or weaken it.TestDaemonStopRefusesWithActiveRunsAndListsThem, TestDaemonStopForceOverridesActiveRunGuard, TestDaemonRestartRefusesWithActiveRuns, TestLifecycleCommandsWriteCallerAttributionToCLILog (internal/cli/daemon_lifecycle_test.go), TestUpdaterRunRefusesWithActiveRunsAndListsThem, TestUpdaterActiveRunGuardAllowsForce (internal/update).9a464c2
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.