multica-ai/multica · error

runtime profile is disabled

Error message

runtime profile is disabled

What it means

errRuntimeProfileDisabled is the sentinel returned by upsertRuntimeWithProfile when a daemon tries to register (upsert) a custom runtime against a runtime profile that has been disabled. Registration is serialized with profile deletion via row locks (the profile row holds a KEY SHARE lock until the upsert commits), so this error means the profile was found but is not in an enabled state — the daemon cannot bring up an instance for it. Callers match on this sentinel to return a distinct 'profile disabled' response rather than a generic failure.

Source

Thrown at server/internal/handler/daemon.go:312

	})
	if err != nil {
		return rt
	}
	shared, ok := sharedDaemonCustomName(names)
	if !ok {
		return rt
	}
	updated, err := h.Queries.UpdateAgentRuntimeCustomName(ctx, db.UpdateAgentRuntimeCustomNameParams{
		CustomName: pgtype.Text{String: shared, Valid: true},
		ID:         rt.ID,
	})
	if err != nil {
		return rt
	}
	return updated
}

var errRuntimeProfileDisabled = errors.New("runtime profile is disabled")

// upsertRuntimeWithProfile serializes custom-runtime registration with profile
// deletion. The profile row remains KEY SHARE locked until the runtime upsert
// commits; DeleteRuntimeProfile takes a conflicting UPDATE lock before it
// enumerates runtime rows. This closes the stale-read window where deletion
// could miss an instance inserted by a concurrently registering daemon.
func (h *Handler) upsertRuntimeWithProfile(
	ctx context.Context,
	workspaceID, profileID pgtype.UUID,
	build func(db.RuntimeProfile) db.UpsertAgentRuntimeWithProfileParams,
) (db.UpsertAgentRuntimeWithProfileRow, db.RuntimeProfile, error) {
	var row db.UpsertAgentRuntimeWithProfileRow
	var profile db.RuntimeProfile

	tx, err := h.TxStarter.Begin(ctx)
	if err != nil {
		return row, profile, fmt.Errorf("begin profile runtime registration: %w", err)
	}

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. On the daemon, stop retrying registration for that profileID and mark it locally disabled until the profile list refreshes.
  2. Re-fetch the workspace's runtime profile list and drop profiles not enabled.
  3. If the disable was unintentional, re-enable the profile server-side and registration will succeed on the next heartbeat.
  4. Surface the 'profile disabled' distinction to operators instead of logging it as a generic registration failure.

Example fix

// before
for {
    err := registerRuntime(ctx, profileID)
    if err != nil { time.Sleep(retryInterval) } // blind retry loop
}

// after
err := registerRuntime(ctx, profileID)
if errors.Is(err, errRuntimeProfileDisabled) {
    localProfiles.disable(profileID) // stop retrying; wait for profile refresh
    return
}
Defensive patterns

Strategy: try-catch

Try / catch

err := h.upsertRuntimeWithProfile(ctx, workspaceID, profileID, build)
if err != nil {
    if errors.Is(err, errRuntimeProfileDisabled) {
        // profile was disabled between listing and registration:
        // stop retrying this profileID, mark it dead locally,
        // and wait for the next profile list refresh
        daemon.markProfileDisabled(profileID)
        return nil
    }
    return fmt.Errorf("register runtime: %w", err)
}

Prevention

When it happens

Trigger: Daemon heartbeat/register call (PUT or upsert runtime) for a profileID whose profile row exists but is disabled — typically right after an admin disables the profile, or while a DeleteRuntimeProfile is racing the registration (the lock ordering closes the stale-read window, so the daemon gets a clean 'disabled' instead of registering a doomed instance).

Common situations: Admin disables a runtime profile while daemons are still running and auto-registering on heartbeat; a daemon caching an old profile list and retrying registration against a since-disabled profile; rollout flow where profiles are disabled before deletion and lagging daemons hit the window.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/663b4c51d40405a5. Report an issue: GitHub.