router-for-me/CLIProxyAPI · error

failed to save refreshed auth: %w

Error message

failed to save refreshed auth: %w

What it means

fetch_codex_models refreshes tokens successfully, mutates auth.Metadata in memory, then persists via store.Save. If Save fails, the error wraps as `failed to save refreshed auth: %w` — the refresh worked (and the rotated refresh token may now be consumed upstream) but the new credential never reached disk/store, so the stale file remains.

Source

Thrown at cmd/fetch_codex_models/main.go:225

		auth.Metadata = make(map[string]any)
	}
	auth.Metadata["id_token"] = tokenData.IDToken
	auth.Metadata["access_token"] = tokenData.AccessToken
	if tokenData.RefreshToken != "" {
		auth.Metadata["refresh_token"] = tokenData.RefreshToken
	}
	if tokenData.AccountID != "" {
		auth.Metadata["account_id"] = tokenData.AccountID
	}
	if tokenData.Email != "" {
		auth.Metadata["email"] = tokenData.Email
	}
	auth.Metadata["expired"] = tokenData.Expire
	auth.Metadata["type"] = "codex"
	auth.Metadata["last_refresh"] = time.Now().Format(time.RFC3339)

	if _, errSave := store.Save(ctx, auth); errSave != nil {
		return "", false, fmt.Errorf("failed to save refreshed auth: %w", errSave)
	}

	return tokenData.AccessToken, true, nil
}

func fetchModels(ctx context.Context, auth *coreauth.Auth, accessToken, clientVersion string) ([]byte, int, error) {
	modelsURL, errURL := codexModelsURL(clientVersion)
	if errURL != nil {
		return nil, 0, errURL
	}

	httpReq, errReq := http.NewRequestWithContext(ctx, http.MethodGet, modelsURL, nil)
	if errReq != nil {
		return nil, 0, errReq
	}
	httpReq.Close = true
	httpReq.Header.Set("Accept", "application/json")
	httpReq.Header.Set("Authorization", "Bearer "+accessToken)

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Check the wrapped error's cause: permission denied -> fix ownership/permissions of auths/ (chown/chmod); IO error -> check disk space.
  2. Verify the store backend: if PGSTORE_*/GITSTORE_*/OBJECTSTORE_* is configured, ensure the backend is reachable and credentials valid.
  3. Re-run the tool after fixing write access — and re-login if the old refresh token was already rotated and lost.
  4. In containers, mount auths/ as a writable volume owned by the process user.

Example fix

# before: read-only auth dir
$ ls -ld auths  # dr-x------ root root

# after: writable by the runtime user
$ chown -R $(id -u):$(id -g) auths && chmod u+w auths
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: can we write to the auth store location?
func authStoreWritable(authDir string) bool {
    probe := filepath.Join(authDir, ".write-probe")
    return os.WriteFile(probe, []byte("x"), 0o600) == nil && os.Remove(probe) == nil
}

Type guard

func isAuthSaveFailure(err error) bool {
    return err != nil && strings.Contains(err.Error(), "failed to save refreshed auth")
}

Prevention

When it happens

Trigger: auths/ directory or auth file is read-only or owned by another user; disk full; Postgres/git/object-store backend (PGSTORE_*/GITSTORE_*/OBJECTSTORE_* env) unreachable or schema-locked; file locked by a concurrent process; container with a read-only volume mount.

Common situations: Running the tool in Docker without write permission on the mounted auths volume; SELinux/AppArmor denying writes; running as a different user than the one that created auths/; storage backend env vars set but backend down.

Related errors


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