{"record":{"id":"9ba4904dd7e09cff","repo":"ipfs/kubo","slug":"repo-is-closed","errorCode":null,"errorMessage":"repo is closed","messagePattern":"repo is closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"repo/fsrepo/fsrepo.go","lineNumber":544,"sourceCode":"func (r *FSRepo) readSpec() (string, error) {\n\tfn, err := config.Path(r.path, specFn)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tb, err := os.ReadFile(fn)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn strings.TrimSpace(string(b)), nil\n}\n\n// Close closes the FSRepo, releasing held resources.\nfunc (r *FSRepo) Close() error {\n\tpackageLock.Lock()\n\tdefer packageLock.Unlock()\n\n\tif r.closed {\n\t\treturn errors.New(\"repo is closed\")\n\t}\n\n\terr := os.Remove(filepath.Join(r.path, apiFile))\n\tif err != nil && !os.IsNotExist(err) {\n\t\tlog.Warn(\"error removing api file: \", err)\n\t}\n\n\terr = os.Remove(filepath.Join(r.path, gatewayFile))\n\tif err != nil && !os.IsNotExist(err) {\n\t\tlog.Warn(\"error removing gateway file: \", err)\n\t}\n\n\tif err := r.ds.Close(); err != nil {\n\t\treturn err\n\t}\n\n\t// This code existed in the previous versions, but\n\t// EventlogComponent.Close was never called. Preserving here","sourceCodeStart":526,"sourceCodeEnd":562,"githubUrl":"https://github.com/ipfs/kubo/blob/329838acdfafae224582930457efe80aa217afc0/repo/fsrepo/fsrepo.go#L526-L562","documentation":"FSRepo.Close returns this when the repo has already been closed (r.closed is true). The repo tracks a closed flag guarded by packageLock, and any Close after the first is rejected. It is a lifecycle misuse signal: close once, in one place.","triggerScenarios":"Calling Close() twice on the same *FSRepo, e.g. explicit Close followed by a deferred Close in a cleanup path, or a shutdown handler racing a deferred close.","commonSituations":"Daemon shutdown code and tests that defer fsrepo.Close() while also calling it explicitly; wrapping a repo in a struct with its own Close that delegates to FSRepo.Close and is invoked by two owners.","solutions":["Track whether your code already closed the repo before calling Close again","Use sync.Once around the Close call","Route all shutdown through a single owner function so Close is called exactly once"],"exampleFix":"// before\nrepo.Close()\ndefer repo.Close()\n// after\nvar closeOnce sync.Once\nclose := func() { closeOnce.Do(func() { repo.Close() }) }\nclose()\ndefer close()","handlingStrategy":"try-catch","validationCode":"// track state yourself\nif repoAlreadyClosed { return nil }","typeGuard":null,"tryCatchPattern":"if err := repo.Close(); err != nil {\n    if err.Error() == \"repo is closed\" { return nil } // already closed\n    return err\n}","preventionTips":["Wrap Close in sync.Once","Keep a single owner of the repo lifecycle","Never call Close from both defer and explicit paths"],"tags":["go","lifecycle","repo"],"backgroundTag":"resource-already-closed","analyzedSha":"329838acdfafae224582930457efe80aa217afc0","analyzedAt":"2026-09-03T18:30:52.135Z","contentChangedAt":"2026-09-03T18:30:52.135Z","schemaVersion":2},"datasetVersion":"2026-09-11T00:17:11.886Z"}