{"record":{"id":"1a25ed10419639c7","repo":"router-for-me/CLIProxyAPI","slug":"failed-to-write-token-to-file-w-1a25ed","errorCode":null,"errorMessage":"failed to write token to file: %w","messagePattern":"failed to write token to file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/kimi/token.go","lineNumber":110,"sourceCode":"\tdata, errMerge := misc.MergeMetadata(ts, ts.Metadata)\n\tif errMerge != nil {\n\t\treturn fmt.Errorf(\"failed to merge metadata: %w\", errMerge)\n\t}\n\n\tf, err := os.Create(authFilePath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create token file: %w\", err)\n\t}\n\tdefer func() {\n\t\tif errClose := f.Close(); errClose != nil {\n\t\t\tlog.Errorf(\"kimi token storage: close token file error: %v\", errClose)\n\t\t}\n\t}()\n\n\tencoder := json.NewEncoder(f)\n\tencoder.SetIndent(\"\", \"  \")\n\tif err = encoder.Encode(data); err != nil {\n\t\treturn fmt.Errorf(\"failed to write token to file: %w\", err)\n\t}\n\treturn nil\n}\n\n// IsExpired checks if the token has expired.\nfunc (ts *KimiTokenStorage) IsExpired() bool {\n\tif ts.Expired == \"\" {\n\t\treturn false // No expiry set, assume valid\n\t}\n\tt, err := time.Parse(time.RFC3339, ts.Expired)\n\tif err != nil {\n\t\treturn true // Has expiry string but can't parse\n\t}\n\t// Consider expired if within refresh threshold\n\treturn time.Now().Add(time.Duration(refreshThresholdSeconds) * time.Second).After(t)\n}\n\n// NeedsRefresh checks if the token should be refreshed.","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/kimi/token.go#L92-L128","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"var stat syscall.Statfs_t\nif err := syscall.Statfs(filepath.Dir(authFilePath), &stat); err == nil && stat.Bavail == 0 {\n    return fmt.Errorf(\"no free space on auth volume\")\n}","typeGuard":null,"tryCatchPattern":"err := ts.SaveTokenToFile(path)\nfor attempt := 1; err != nil && strings.Contains(err.Error(), \"failed to write token\") && attempt < 3; attempt++ {\n    time.Sleep(time.Duration(attempt) * 250 * time.Millisecond) // transient ENOSPC/EIO retry\n    err = ts.SaveTokenToFile(path)\n}","preventionTips":["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"],"tags":["filesystem","json","kimi","auth","disk-full"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}