No-Mistakes Lifecycle Guard: Preventing Daemon Stops and Restarts During Active Runs

The lifecycle guard blocks destructive daemon operations by querying the SQLite database for runs with RunPending or RunRunning status, refusing to stop or restart the daemon unless the --force flag is explicitly provided.

The no-mistakes daemon manages pipeline executions that can span minutes or hours, requiring protection against accidental interruption. The lifecycle guard is a safety mechanism implemented in the Go source code that prevents the daemon from stopping, restarting, or updating while any pipeline run remains active. This design protects worktrees, database consistency, and external resources from corruption due to premature termination.

How the Lifecycle Guard Works

The guard operates through a three-layer defense that detects active runs, enforces protection rules, and integrates with the CLI command handlers.

Active Run Detection

The ActiveRuns function in internal/lifecycle/guard.go opens the local SQLite database and queries for incomplete operations. It returns all runs where the status column equals RunPending or RunRunning, providing the foundation for the guard's decision-making logic.

func ActiveRuns(p *paths.Paths) ([]*db.Run, error)

This function accepts a *paths.Paths struct to locate the database file via p.DB(), then filters the runs table for active states. If no database exists or no active runs are found, it returns an empty slice.

Guard Enforcement Logic

The guardDestructiveDaemonLifecycle function in internal/cli/daemon_cmd.go implements the enforcement policy. Called before any destructive operation, it evaluates the active run list and the force boolean parameter to determine whether to proceed, refuse, or warn.

The logic follows these rules:

  • Empty run list: The operation proceeds immediately
  • Active runs present without --force: Returns an error containing the formatted run list via lifecycle.RunList, causing the CLI to exit with a non-zero status
  • Active runs present with --force: Writes a warning to stderr displaying the active runs, then returns nil to allow the operation

The RunList helper function formats the active runs into a human-readable multi-line string showing run IDs, statuses, branches, and commit hashes.

CLI Integration

The guard is wired directly into the Cobra command handlers for daemon stop, daemon restart, and update --force in internal/cli/daemon_cmd.go. Both newDaemonStopCmd and newDaemonRestartCmd invoke guardDestructiveDaemonLifecycle before executing their destructive actions, ensuring the check occurs at the entry point of every relevant CLI operation.

Implementation Details

The lifecycle guard spans three critical files in the repository:

  • internal/lifecycle/guard.go: Contains ActiveRuns for database queries and RunList for formatting output
  • internal/cli/daemon_cmd.go: Houses guardDestructiveDaemonLifecycle and the command handlers that enforce the guard
  • internal/cli/daemon_lifecycle_test.go: Provides test coverage through TestDaemonStopRefusesWithActiveRunsAndListsThem and TestDaemonRestartRefusesWithActiveRuns, verifying that commands fail appropriately without --force and succeed with it

The guard specifically checks for the status constants RunPending and RunRunning defined in the database package, ensuring that only truly active operations trigger the protection.

Practical Usage Examples

Checking for Active Runs Programmatically

You can leverage the lifecycle guard's detection logic in your own Go code to implement custom pre-flight checks:

import (
    "fmt"
    "github.com/kunchenguid/no-mistakes/internal/lifecycle"
    "github.com/kunchenguid/no-mistakes/internal/paths"
)

func validateSafeShutdown() error {
    p, _ := paths.New()
    runs, err := lifecycle.ActiveRuns(p)
    if err != nil {
        return fmt.Errorf("database error: %w", err)
    }
    if len(runs) == 0 {
        return nil // Safe to proceed
    }
    return fmt.Errorf("cannot stop daemon: %d active runs in progress\n%s",
        len(runs), lifecycle.RunList(runs))
}

Default Safe Behavior (CLI)

Without additional flags, the CLI refuses destructive operations when pipelines are active:

$ no-mistakes daemon stop
refusing daemon stop because 2 active pipeline runs are in progress;
active pipeline runs:
  aaa111  pending  feature-a  a1b2c3d4
  bbb222  running  feature-b  b2c3d4e5

The command exits with a non-zero status code, preventing automation scripts from accidentally interrupting running work.

Overriding the Guard with Force

When you must stop or restart the daemon despite active runs, use the --force flag:

$ no-mistakes daemon stop --force
FORCE: daemon stop will stop the daemon while 2 active pipeline runs are in progress
active pipeline runs:
  aaa111  pending  feature-a  a1b2c3d4
  bbb222  running  feature-b  b2c3d4e5
daemon stopped

The same pattern applies to restarts:

$ no-mistakes daemon restart --force
FORCE: daemon restart will stop/restart the daemon while 1 active pipeline run is in progress
active pipeline runs:
  ccc333  running  feature-c  c3d4e5f6
daemon stopped
daemon started

Summary

  • The lifecycle guard in no-mistakes prevents daemon stops, restarts, and forced updates while pipeline runs are active
  • ActiveRuns in internal/lifecycle/guard.go queries the SQLite database for runs with RunPending or RunRunning status
  • guardDestructiveDaemonLifecycle enforces the guard logic in internal/cli/daemon_cmd.go, returning errors unless --force is specified
  • The --force flag bypasses the guard after printing a warning to stderr, allowing intentional overrides
  • This mechanism protects worktrees, database state, and external resources from inconsistency caused by premature daemon termination

Frequently Asked Questions

What statuses trigger the lifecycle guard?

The guard triggers when any run has the status RunPending or RunRunning. These constants represent operations that have been queued but not started, or are currently executing. Completed, failed, or canceled runs do not trigger the protection.

Where is the lifecycle guard implemented in the source code?

The detection logic resides in internal/lifecycle/guard.go via the ActiveRuns function, while the enforcement logic lives in internal/cli/daemon_cmd.go within guardDestructiveDaemonLifecycle. The guard is invoked by the daemon stop, daemon restart, and update --force command handlers in the same file.

How do I stop the daemon when active runs are stuck?

Use the --force flag with your stop or restart command: no-mistakes daemon stop --force. This prints a warning listing the active runs but proceeds with the operation. Only use this when you understand that interrupting active runs may leave worktrees or external resources in an inconsistent state.

Does the lifecycle guard affect the update command?

Yes, the update --force command also invokes guardDestructiveDaemonLifecycle to prevent updates from restarting the daemon while pipelines are running. Without the --force flag on the update command, the guard blocks the update operation if active runs exist.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →