router-for-me/CLIProxyAPI · error
failed to merge metadata: %w
Error message
failed to merge metadata: %w
What it means
Before writing the credential, SaveTokenToFile merges ts.Metadata into the storage struct via misc.MergeMetadata, which round-trips through JSON. This error means that merge failed — typically because the metadata (or an exported field on the token storage) contains a value JSON cannot marshal, such as a channel, func, or cyclic reference.
Source
Thrown at internal/auth/claude/token.go:85
//
// Parameters:
// - authFilePath: The full path where the token file should be saved
//
// Returns:
// - error: An error if the operation fails, nil otherwise
func (ts *ClaudeTokenStorage) SaveTokenToFile(authFilePath string) error {
misc.LogSavingCredentials(authFilePath)
ts.Type = "claude"
// Create directory structure if it doesn't exist
if err := os.MkdirAll(filepath.Dir(authFilePath), 0700); err != nil {
return fmt.Errorf("failed to create directory: %v", err)
}
// Merge metadata using helper
data, errMerge := misc.MergeMetadata(ts, ts.Metadata)
if errMerge != nil {
return fmt.Errorf("failed to merge metadata: %w", errMerge)
}
// Create the token file
f, err := os.Create(authFilePath)
if err != nil {
return fmt.Errorf("failed to create token file: %w", err)
}
defer func() {
if errClose := f.Close(); errClose != nil {
log.Errorf("claude token storage: close token file error: %v", errClose)
}
}()
// Encode and write the token data as JSON
if err = json.NewEncoder(f).Encode(data); err != nil {
return fmt.Errorf("failed to write token to file: %w", err)
}
return nilView on GitHub (pinned to 78f0c4079e)
Solutions
- Inspect ts.Metadata and ensure every value is JSON-serializable (strings, numbers, bools, plain maps/slices).
- Keep runtime objects (loggers, hooks) in separate struct fields tagged json:"-" rather than in Metadata.
- Log the wrapped marshal error to identify the offending key/type.
Example fix
// before storage.Metadata["cancel"] = someFunc // after storage.Metadata["note"] = "renewed 2026-08-15" // metadata holds JSON values only
Defensive patterns
Strategy: validation
Validate before calling
if _, err := json.Marshal(ts.Metadata); err != nil {
return fmt.Errorf("metadata must be JSON-serializable: %w", err)
}
err := ts.SaveTokenToFile(path) Type guard
func jsonSafe(v any) bool { _, err := json.Marshal(v); return err == nil } Try / catch
if err := ts.SaveTokenToFile(p); err != nil && strings.Contains(err.Error(), "merge metadata") {
ts.Metadata = map[string]any{} // strip non-serializable extras and retry
err = ts.SaveTokenToFile(p)
} Prevention
- Keep Metadata limited to values that came from JSON.
- Store runtime objects (loggers, hooks) in fields tagged json:"-".
When it happens
Trigger: A ClaudeTokenStorage whose Metadata map was populated with non-serializable values (func, chan, complex, or a cyclic struct) before SaveTokenToFile is called; metadata injected programmatically by an embedding SDK consumer rather than from parsed OAuth JSON.
Common situations: SDK/embedding users attaching runtime objects (loggers, callbacks) into Metadata instead of plain JSON values; custom builds that add struct fields of non-JSON types to ClaudeTokenStorage; virtually never happens in the stock CLI flow where metadata comes from JSON responses.
Related errors
- failed to write token to file: %w
- failed to merge metadata: %w
- failed to create directory: %v
- failed to create token file: %w
- failed to merge metadata: %w
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/78c261191bcd8b42.
Report an issue: GitHub.