router-for-me/CLIProxyAPI · error
failed to merge metadata: %w
Error message
failed to merge metadata: %w
What it means
Returned when misc.MergeMetadata fails while serializing the Kimi token storage together with its Metadata map before writing the JSON auth file (token.go:92-95). MergeMetadata merges the struct and its metadata into a single JSON-serializable value, so failure almost always means the Metadata map contains a value type that json.Marshal cannot encode (e.g. channels, funcs, cycles).
Source
Thrown at internal/auth/kimi/token.go:94
// ExpiresIn is the number of seconds until the device code expires.
ExpiresIn int `json:"expires_in"`
// Interval is the minimum number of seconds to wait between polling requests.
Interval int `json:"interval"`
}
// SaveTokenToFile serializes the Kimi token storage to a JSON file.
func (ts *KimiTokenStorage) SaveTokenToFile(authFilePath string) error {
misc.LogSavingCredentials(authFilePath)
ts.Type = "kimi"
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)
}
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("kimi token storage: close token file error: %v", errClose)
}
}()
encoder := json.NewEncoder(f)
encoder.SetIndent("", " ")
if err = encoder.Encode(data); err != nil {
return fmt.Errorf("failed to write token to file: %w", err)
}
return nilView on GitHub (pinned to 78f0c4079e)
Solutions
- Inspect what keys/values are placed into KimiTokenStorage.Metadata and restrict them to plain JSON types (string, number, bool, nested maps/slices)
- If embedding the SDK, JSON-encode exotic values to strings before storing them as metadata
- Reproduce with a minimal SaveTokenToFile call and check the wrapped %w cause for the exact marshal error
Example fix
// before
ts.Metadata = map[string]any{"done": make(chan struct{})}
// after
ts.Metadata = map[string]any{"done": "closed"} Defensive patterns
Strategy: validation
Validate before calling
for k, v := range ts.Metadata {
if !isJSONScalarOrContainer(v) {
return fmt.Errorf("metadata key %q holds a non-serializable value", k)
}
} Try / catch
if err := ts.SaveTokenToFile(path); err != nil && strings.Contains(err.Error(), "failed to merge metadata") {
log.Errorf("unserializable kimi metadata; dropping metadata and retrying")
ts.Metadata = nil
err = ts.SaveTokenToFile(path)
} Prevention
- Only store JSON-native values in Metadata
- Stringify custom objects before assigning them as metadata
- Unit-test SaveTokenToFile with the exact metadata you plan to attach
When it happens
Trigger: KimiTokenStorage.Metadata populated with non-serializable values (func, chan, complex, or cyclic pointer structures) before SaveTokenToFile is called; extremely large metadata causing a marshaler panic captured as an error.
Common situations: Custom code embedding the SDK (sdk/cliproxy) that stuffs arbitrary Go values into Metadata; upstream code attaching an unserializable context-like object; virtually never happens with CLIProxyAPI's own metadata usage.
Related errors
- failed to write token to file: %w
- vertex credential: merge metadata failed: %w
- invalid auth file: %w
- source auth path is empty
- invalid auth file: %w
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/c92ed611f8e01c0f.
Report an issue: GitHub.