Skip to content

Setup wizard persists Symbol(clack:cancel) as token when user cancels manual token entry #38928

@chrisfonte

Description

@chrisfonte

Bug Report

Summary

When the setup wizard prompts for a manual API token and the user cancels the prompt (Ctrl+C / Escape), the clack library's cancel symbol (Symbol(clack:cancel)) is coerced to the string "Symbol(clack:cancel)" and written to auth-profiles.json as the token value. The profile entry persists with this bogus string rather than being discarded.

Behavior

In auth-profiles.json, the resulting entry looks like:

"openai:manual": {
  "type": "token",
  "provider": "openai",
  "token": "Symbol(clack:cancel)"
}

Impact

If openclaw ever selects this profile as a fallback (e.g., when a primary auth provider is temporarily unavailable), it sends Symbol(clack:cancel) as the Bearer token and receives a 401:

openai embeddings failed: 401 {
  "error": {
    "message": "Incorrect API key provided: Symbol(c********cel).",
    "type": "invalid_request_error",
    "code": "invalid_api_key"
  }
}

In our case: BWS (Bitwarden Secrets) was temporarily degraded → Gemini embedding auth failed → openclaw fell back to openai:manual → 401 from OpenAI embeddings API.

Expected Behavior

When the user cancels token entry during the wizard, the profile should either:

  1. Not be written to auth-profiles.json at all, or
  2. Be written with token: null and skipped when selected (no 401 attempt)

Steps to Reproduce

  1. Run openclaw configure (or equivalent wizard)
  2. When prompted to enter a manual API token for any provider, press Ctrl+C or Escape
  3. Check ~/.openclaw/agents/<agent>/agent/auth-profiles.json
  4. The profile entry will have "token": "Symbol(clack:cancel)"

Environment

  • OpenClaw version: 2026.3.2
  • macOS Darwin 24.3.0 arm64

Workaround

Manually null out the token in all affected auth-profiles.json files:

import json, glob
for f in glob.glob('/path/to/.openclaw/agents/*/agent/auth-profiles.json'):
    with open(f) as fh: data = json.load(fh)
    for profile in data.get('profiles', {}).values():
        if profile.get('token') == 'Symbol(clack:cancel)':
            profile['token'] = None
    with open(f, 'w') as fh: json.dump(data, fh, indent=2)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions