crowdsecurity/crowdsec · error

unable to update machine in database: %w

Error message

unable to update machine in database: %w

What it means

UpdateMachineScenarios returns the raw ent error (no sentinel) when Machine.UpdateOneID(id) fails while persisting the machine's scenario list. This is called during Authenticator, i.e. when a validated watcher authenticates; a stale/unknown row id makes ent return a not-found error here. Note the message text is slightly off — it's a generic update failure, not 'machine' specific.

Source

Thrown at pkg/database/machines.go:207

	return nbDeleted, nil
}

func (c *Client) UpdateMachineLastHeartBeat(ctx context.Context, machineID string) error {
	_, err := c.Ent.Machine.Update().Where(machine.MachineIdEQ(machineID)).SetLastHeartbeat(time.Now().UTC()).Save(ctx)
	if err != nil {
		return fmt.Errorf("updating machine last_heartbeat: %w: %w", err, UpdateFail)
	}

	return nil
}

func (c *Client) UpdateMachineScenarios(ctx context.Context, scenarios string, id int) error {
	_, err := c.Ent.Machine.UpdateOneID(id).
		SetUpdatedAt(time.Now().UTC()).
		SetScenarios(scenarios).
		Save(ctx)
	if err != nil {
		return fmt.Errorf("unable to update machine in database: %w", err)
	}

	return nil
}

func (c *Client) UpdateMachineIP(ctx context.Context, ipAddr string, id int) error {
	_, err := c.Ent.Machine.UpdateOneID(id).
		SetIpAddress(ipAddr).
		Save(ctx)
	if err != nil {
		return fmt.Errorf("unable to update machine IP in database: %w", err)
	}

	return nil
}

func (c *Client) UpdateMachineVersion(ctx context.Context, ipAddr string, id int) error {
	_, err := c.Ent.Machine.UpdateOneID(id).

View on GitHub (pinned to 909b515798)

Solutions

  1. Re-check the machine still exists (QueryMachineByID) before/after the update and handle deletion races gracefully
  2. Inspect the wrapped error: ent not-found vs connection failure lead to different fixes
  3. If races with prune/delete are common, re-fetch the machine row and retry once
  4. Ensure DB is writable and the schema matches (`cscli migration`)

Example fix

// before
if err := db.UpdateMachineScenarios(ctx, scenarios, machine.ID); err != nil {
    return err
}
// after
if err := db.UpdateMachineScenarios(ctx, scenarios, machine.ID); err != nil {
    if _, qerr := db.QueryMachineByID(ctx, machine.MachineId); errors.Is(qerr, database.UserNotExists) {
        return nil // machine deleted concurrently; nothing to update
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// refresh the row before the id-based update
m, err := db.QueryMachineByID(ctx, machineID)
if err != nil { return err }
err = db.UpdateMachineScenarios(ctx, scenarios, m.ID)

Try / catch

if err := db.UpdateMachineScenarios(ctx, scenarios, id); err != nil {
    if _, qerr := db.QueryMachineByID(ctx, machineID); errors.Is(qerr, database.UserNotExists) {
        return nil // benign: machine deleted concurrently
    }
    return err
}

Prevention

When it happens

Trigger: Authenticator detects the machine's scenario list changed and calls UpdateMachineScenarios, but the row with the given auto-increment id was deleted concurrently, or the DB write fails (lock, connection).

Common situations: Machine deleted (prune/`cscli machines delete`) while its heartbeat/auth was in flight; DB busy on SQLite; id passed from a stale cached row.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/5846e30b30f63420. Report an issue: GitHub.