# How no-mistakes Stores Run State in SQLite: Inside internal/db

> Discover how no-mistakes stores run state in SQLite using the internal/db package. Learn about atomic helper functions for managing pipeline execution lifecycles from pending to completed.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: internals
- Published: 2026-07-03

---

**no-mistakes persists every pipeline execution in a local SQLite database using the `runs` table defined in [`internal/db/schema.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/schema.go), with atomic helper functions in [`internal/db/run.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/run.go) managing the lifecycle from pending to completed.**

The `no-mistakes` repository implements a zero-configuration SQLite storage layer to maintain durable records of pipeline runs. Located in the `internal/db` package, this module captures the entire execution lifecycle—from initial creation through terminal states—without requiring external database infrastructure. Understanding how the `runs` table schema and the data-access layer manage state transitions is essential for extending the executor or debugging run persistence.

## The `runs` Table Schema in internal/db/schema.go

### Core Identifier and Git Reference Columns

The `runs` table is created by the `migrateRuns()` function in [`internal/db/schema.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/schema.go) using an `INTEGER PRIMARY KEY AUTOINCREMENT` for unique run identification. It stores Git context through four critical columns: `repo_id` (a foreign key to the `repos` table), `branch`, `head_sha` (the commit SHA checked out when the run started), and `base_sha` (the commit SHA of the remote default at run creation). These columns ensure every run record is immutably tied to specific repository state and version control metadata.

### Status and Temporal Tracking

Run progression is tracked through the `status` column, which accepts enumerated values: `pending`, `running`, `passed`, `failed`, or `cancelled`. Temporal tracking uses `started_at` and `completed_at` datetime fields. The nullable `awaiting_agent_since` column specifically marks when a run is parked at a gate awaiting human or automated agent review, enabling the CLI to render "parked" badges without polling the executor process.

### Flexible JSON Payloads

The schema accommodates extensible data through two JSON text columns. The `result` column stores the final execution summary—including step outcomes and diagnostics—while `metadata` provides a free-form field for plugin-specific data. This design allows agents to persist arbitrary structured data without requiring schema migrations.

## State Lifecycle Management in internal/db/run.go

The [`internal/db/run.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/run.go) file provides thin wrappers around `sqlx` queries that encapsulate all reads and writes to the `runs` table.

### Run Creation and Initialization

When a user initiates a pipeline via `no-mistakes run`, the executor calls `InsertRun()`. This function populates `repo_id`, `branch`, `head_sha`, and `base_sha`, setting `status` to `"pending"` and recording `started_at`. The function returns the generated `id` for subsequent state updates.

### Transitioning to Active Execution

As the executor begins processing steps, it invokes `SetRunStatus(ctx, id, "running")` to update the status column. This transition marks the shift from queued to active execution, allowing the CLI to display real-time run activity.

### Agent Gate Handling

Gate events utilize the `awaiting_agent_since` column for persistent parking state. When a run reaches a review gate, `SetRunAwaitingAgent(ctx, id, now)` populates this timestamp. Once the agent resolves the gate, `ClearRunAwaitingAgent(ctx, id)` nulls the field, guaranteeing the flag is only set while the run is genuinely awaiting an agent.

### Terminal State Persistence

Completion requires three coordinated operations: `SetRunStatus()` updates the terminal state (`"passed"` or `"failed"`), `SetRunCompletedAt()` records the finish timestamp, and `UpdateRunResult()` persists the JSON-encoded execution summary. These operations ensure atomic state capture for post-run analysis.

## Database Initialization and Configuration

The SQLite database file resides at `$NM_HOME/state.db`, initialized by `db.New()` in [`internal/db/db.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/db.go). This function enables the `foreign_keys = ON` pragma for referential integrity between `runs.repo_id` and `repos.id`, and enables WAL mode for concurrent read/write performance. The `migrateRuns()` function executes the `CREATE TABLE` statement if the `runs` table does not exist.

## Related Schema and Foreign Keys

The `runs` table participates in a relational schema managed by [`internal/db/schema.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/schema.go). The `repos` table stores repository configuration, while `rounds` groups steps that belong to the same retryable execution batch. Individual `steps` maintain their own status records in a separate table, and `intent_cache` persists agent analysis to avoid recomputation. Foreign key constraints ensure that runs cannot reference non-existent repositories.

## Practical Code Examples

### Creating and Updating a Run

```go
// Create a new run (invoked when a user runs `no-mistakes run …`)
runID, err := db.InsertRun(ctx, internaldb.Run{
    RepoID:     repo.ID,
    Branch:     branch,
    HeadSHA:    headSHA,
    BaseSHA:    baseSHA,
    Status:     "pending",
    StartedAt:  time.Now(),
})
if err != nil {
    log.Fatalf("cannot create run: %v", err)
}

// Mark the run as awaiting an agent (e.g. at a review gate)
if err := db.SetRunAwaitingAgent(ctx, runID, time.Now()); err != nil {
    log.Fatalf("cannot set awaiting‑agent flag: %v", err)
}

// When the agent replies, clear the flag
if err := db.ClearRunAwaitingAgent(ctx, runID); err != nil {
    log.Fatalf("cannot clear awaiting‑agent flag: %v", err)
}

// Finish the run – store final status and result JSON
finalResult := map[string]any{
    "steps": stepSummaries,
}
resultJSON, _ := json.Marshal(finalResult)

if err := db.UpdateRunResult(ctx, runID, string(resultJSON)); err != nil {
    log.Fatalf("cannot store result: %v", err)
}
if err := db.SetRunStatus(ctx, runID, "passed"); err != nil {
    log.Fatalf("cannot set final status: %v", err)
}
if err := db.SetRunCompletedAt(ctx, runID, time.Now()); err != nil {
    log.Fatalf("cannot set completed_at: %v", err)
}

```

### Querying Current Run State

```go
// Query the current run state (used by `axi status`)
run, err := db.GetRun(ctx, runID)
if err != nil {
    log.Fatalf("run not found: %v", err)
}
fmt.Printf("Run %d – %s (awaiting agent: %v)\n",
    run.ID, run.Status, run.AwaitingAgentSince != nil)

```

## Summary

- The `runs` table in [`internal/db/schema.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/schema.go) defines the canonical schema for pipeline execution state, including Git refs, timestamps, and JSON result payloads.
- **State transitions** are atomic operations handled by [`internal/db/run.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/run.go) functions like `SetRunStatus`, `SetRunAwaitingAgent`, and `UpdateRunResult`.
- The **`awaiting_agent_since`** column enables persistent gate-tracking without process polling, supporting the "parked" badge rendered by [`internal/cli/axi_render.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/axi_render.go).
- **SQLite** provides zero-configuration persistence at `$NM_HOME/state.db` with foreign key enforcement and WAL mode enabled in `db.New()`.
- Related tables (`repos`, `rounds`, `steps`) provide relational context while JSON columns ensure schema flexibility for agent-specific data.

## Frequently Asked Questions

### Where is the SQLite database file located?

The database file is stored at `$NM_HOME/state.db`, where `$NM_HOME` resolves to the user's home directory or a configured environment path. This location is determined during initialization in [`internal/db/db.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/db.go).

### How does the executor differentiate between a run that is running versus one awaiting an agent?

The executor distinguishes these states through the `status` column and `awaiting_agent_since` timestamp. A run with `status = "running"` and a non-null `awaiting_agent_since` indicates active execution paused at a gate, while `status = "pending"` indicates the run has not yet started processing steps.

### Can I query the run state directly using the sqlite3 command-line tool?

Yes, you can open `$NM_HOME/state.db` with any SQLite client. The `runs` table contains human-readable columns like `head_sha` and `status`, though the `result` and `metadata` columns contain JSON text that requires parsing or filtering using SQLite's JSON1 extension functions.

### What happens if the result JSON exceeds SQLite's storage limits?

SQLite's TEXT type has a theoretical limit of 2GB, which is sufficient for typical pipeline summaries. The `no-mistakes` architecture treats the `result` column as a completion blob rather than granular step logs, with detailed logs potentially stored elsewhere or referenced by ID to avoid size constraints.