router-for-me/CLIProxyAPI · error

failed to update auth %s: %w

Error message

failed to update auth %s: %w

What it means

After the source file's disabled flag was persisted, propagating the state to every runtime auth expanded from that source failed at authManager.Update for the auth named by %s. The file on disk and the in-memory registry can now be out of sync — the file says disabled but that one runtime auth record could not be updated.

Source

Thrown at internal/api/handlers/management/auth_files_fields.go:148

	if errWrite := setSourceAuthFileDisabled(sourcePath, disabled); errWrite != nil {
		if os.IsNotExist(errWrite) {
			return errAuthFileNotFound
		}
		return fmt.Errorf("failed to update source auth file: %w", errWrite)
	}
	now := time.Now()
	for _, auth := range h.authManager.List() {
		if auth == nil {
			continue
		}
		if !sameAuthFilePath(authAttribute(auth, "path"), sourcePath) &&
			!sameAuthFilePath(authAttribute(auth, coreauth.AttributeVirtualSource), sourcePath) {
			continue
		}
		applyAuthDisabledState(auth, disabled)
		auth.UpdatedAt = now
		if _, errUpdate := h.authManager.Update(ctx, auth); errUpdate != nil {
			return fmt.Errorf("failed to update auth %s: %w", auth.ID, errUpdate)
		}
	}
	return nil
}

func setSourceAuthFileDisabled(path string, disabled bool) error {
	path = strings.TrimSpace(path)
	if path == "" {
		return fmt.Errorf("source auth path is empty")
	}
	data, errRead := os.ReadFile(path)
	if errRead != nil {
		return errRead
	}
	metadata := make(map[string]any)
	if len(bytes.TrimSpace(data)) > 0 {
		if errUnmarshal := json.Unmarshal(data, &metadata); errUnmarshal != nil {
			return fmt.Errorf("invalid auth file: %w", errUnmarshal)

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Retry the toggle request — re-running re-reads the file and re-applies state to all children idempotently
  2. If using a remote store, check its connectivity/health and retry after it recovers
  3. Avoid concurrent toggle and rotate/delete operations on the same source; sequence them
  4. Verify final state via GET on the auth list and manually toggle again if any child drifted

Example fix

# before: concurrent
curl -X PATCH .../disable & curl -X POST .../rotate &
# after: sequential
curl -X POST .../rotate && curl -X PATCH .../disable
Defensive patterns

Strategy: retry

Try / catch

err := patchPluginVirtualSourceStatus(ctx, auth, disabled)
if err != nil && strings.Contains(err.Error(), "failed to update auth") {
    // source file persisted; a retry reconciles runtime children idempotently
    auth = refreshAuthFromManager(auth)
    err = patchPluginVirtualSourceStatus(ctx, auth, disabled)
}

Prevention

When it happens

Trigger: PATCH toggle on a plugin virtual source where one of its expanded child auths is concurrently deleted or rotated out between List() and Update(); a store-level failure (Postgres connection drop when using PGSTORE_*, file lock contention on the file store).

Common situations: Two admins toggling/rotating credentials simultaneously; background token refresh re-writing the auth concurrently; external store (PG/git/object store) briefly unavailable mid-operation.

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/478ccfa9683e2318. Report an issue: GitHub.