{"record":{"id":"ccce1de294a3092c","repo":"fatedier/frp","slug":"failed-to-persist-w","errorCode":null,"errorMessage":"failed to persist: %w","messagePattern":"failed to persist: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/source/store.go","lineNumber":183,"sourceCode":"\t}\n\n\tif err := f.Close(); err != nil {\n\t\tos.Remove(tmpPath)\n\t\treturn fmt.Errorf(\"failed to close temp file: %w\", err)\n\t}\n\n\tif err := os.Rename(tmpPath, s.config.Path); err != nil {\n\t\tos.Remove(tmpPath)\n\t\treturn fmt.Errorf(\"failed to rename temp file: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (s *StoreSource) persistOrRollbackUnlocked(rollback func()) error {\n\tif err := s.saveToFileUnlocked(); err != nil {\n\t\trollback()\n\t\treturn fmt.Errorf(\"failed to persist: %w\", err)\n\t}\n\treturn nil\n}\n\n// Store map selectors return the target map for generic helpers.\nfunc proxyStoreEntries(s *StoreSource) map[string]v1.ProxyConfigurer {\n\treturn s.proxies\n}\n\nfunc visitorStoreEntries(s *StoreSource) map[string]v1.VisitorConfigurer {\n\treturn s.visitors\n}\n\n// Store entry helpers share mutation, persistence, and rollback for proxy and visitor maps.\n// T is intentionally limited by callers to v1.ProxyConfigurer or v1.VisitorConfigurer.\nfunc addStoreEntry[T any](\n\ts *StoreSource,\n\tentriesFn func(*StoreSource) map[string]T,","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/config/source/store.go#L165-L201","documentation":"The generic persistence wrapper: any failure from saveToFileUnlocked while committing an Add/Update/Remove operation surfaces here, and the rollback closure undoes the in-memory map change first. This keeps the in-memory store and the file consistent — the mutation is neither applied in memory nor persisted. The real cause is always in the wrapped error chain.","triggerScenarios":"Any of the save-time failures (marshal, MkdirAll, temp-file create/write/sync/close/rename) triggered by AddProxy, AddVisitor, UpdateProxy, UpdateVisitor, RemoveProxy, or RemoveVisitor. E.g. AddProxy on a full disk returns 'failed to persist: failed to write temp file: ...'.","commonSituations":"Disk full, read-only container FS, AV file locking on Windows, permission changes on the store directory after startup.","solutions":["Unwrap the chain to find the root cause: errors.Unwrap / fmt %w already nests it — log err and read the innermost message","Apply the fix for the underlying failure (free disk space, fix permissions, exclude AV, remount rw)","Retry the original Add/Update/Remove call — rollback guarantees no duplicate or half-applied state","If persistence keeps failing, stop making changes and back up the store file before further operations"],"exampleFix":"// before: swallowing the cause\nif err := store.AddProxy(p); err != nil { log.Fatal(\"add failed\") }\n\n// after: surface the root cause\nif err := store.AddProxy(p); err != nil {\n    log.Fatalf(\"add failed: %+v\", err) // prints full chain e.g. write temp file: no space left on device\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"func commit(store *source.StoreSource, op func() error) error {\n\terr := op()\n\tif err == nil {\n\t\treturn nil\n\t}\n\tif errors.Is(err, source.ErrAlreadyExists) || errors.Is(err, source.ErrNotFound) {\n\t\treturn err // semantic conflict: not a persistence failure\n\t}\n\t// persistence failure: root cause is in the chain; state was rolled back\n\tvar pathErr *fs.PathError\n\tif errors.As(err, &pathErr) {\n\t\tlog.Printf(\"store io failure on %s: %v\", pathErr.Path, pathErr.Err)\n\t}\n\treturn err\n}","preventionTips":["Always log the full error chain (%+v / %w) so the underlying save failure is visible","Ensure disk space, directory permissions, and mount rw-ness at startup, not at first write","Because rollback reverts memory, never re-apply a mutation after a persist failure without retrying the same op"],"tags":["persistence","rollback","error-chaining","go"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}