router-for-me/CLIProxyAPI · error
failed to write token to file: %w
Error message
failed to write token to file: %w
What it means
Returned when json.Encoder.Encode fails while writing the merged Kimi token JSON into the opened file (token.go:108-111). Since the data was already produced by MergeMetadata (which marshals), encode failures here are almost always I/O-level: disk full mid-write, file closed prematurely, or quota exceeded.
Source
Thrown at internal/auth/kimi/token.go:110
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 nil
}
// IsExpired checks if the token has expired.
func (ts *KimiTokenStorage) IsExpired() bool {
if ts.Expired == "" {
return false // No expiry set, assume valid
}
t, err := time.Parse(time.RFC3339, ts.Expired)
if err != nil {
return true // Has expiry string but can't parse
}
// Consider expired if within refresh threshold
return time.Now().Add(time.Duration(refreshThresholdSeconds) * time.Second).After(t)
}
// NeedsRefresh checks if the token should be refreshed.View on GitHub (pinned to 78f0c4079e)
Solutions
- Free disk space on the volume holding auths/ (df -h) and retry the operation that triggered the save
- Check filesystem quotas if applicable (quota -s)
- Ensure only one process/goroutine writes the same auth file; the proxy serializes saves, so stop any external writer
- Re-run the Kimi login flow after freeing space so a complete token file is written
Defensive patterns
Strategy: retry
Validate before calling
var stat syscall.Statfs_t
if err := syscall.Statfs(filepath.Dir(authFilePath), &stat); err == nil && stat.Bavail == 0 {
return fmt.Errorf("no free space on auth volume")
} Try / catch
err := ts.SaveTokenToFile(path)
for attempt := 1; err != nil && strings.Contains(err.Error(), "failed to write token") && attempt < 3; attempt++ {
time.Sleep(time.Duration(attempt) * 250 * time.Millisecond) // transient ENOSPC/EIO retry
err = ts.SaveTokenToFile(path)
} Prevention
- Monitor disk space on the auth volume
- Keep an in-memory copy of the token so a failed persist is recoverable
- Write via temp file + rename for atomicity so partial writes never persist
When it happens
Trigger: ENOSPC (disk full) during the write; EDQUOT quota hit; the file was closed concurrently by another goroutine between os.Create and Encode; very rarely, an unserializable value injected into data after the merge.
Common situations: Containers with a small writable layer filling up; long-running hosts with log growth exhausting the disk; concurrent config reload triggering simultaneous token saves to the same path.
Related errors
- invalid auth file: %w
- failed to create directory: %v
- failed to merge metadata: %w
- failed to create token file: %w
- failed to read auth file: %w
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/1a25ed10419639c7.
Report an issue: GitHub.