{"record":{"id":"a87dbc70219a1631","repo":"benbjohnson/litestream","slug":"stage-open","errorCode":"stage-open","errorMessage":"%w: %w (ErrDiskFull)","messagePattern":"%w: %w \\(ErrDiskFull\\)","errorType":"error_code","errorClass":"LTXError","httpStatus":null,"severity":"critical","filePath":"db.go","lineNumber":2123,"sourceCode":"\n\t// Exit if we have no new WAL pages and we aren't snapshotting.\n\tif !info.snapshotting && sz == 0 {\n\t\tdb.Logger.Log(ctx, internal.LevelTrace, \"sync: skip\", \"reason\", \"no new wal pages\")\n\t\treturn result, nil\n\t}\n\n\ttmpFilename := filename + \".tmp\"\n\tif err := internal.MkdirAll(filepath.Dir(tmpFilename), db.dirInfo); err != nil {\n\t\tif isDiskFullError(err) {\n\t\t\treturn result, NewLTXError(\"stage-mkdir\", tmpFilename, 0, uint64(txID), uint64(txID), fmt.Errorf(\"%w: %w\", ErrDiskFull, err))\n\t\t}\n\t\treturn result, err\n\t}\n\n\tltxFile, err := db.openLTXFile(tmpFilename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)\n\tif err != nil {\n\t\tif isDiskFullError(err) {\n\t\t\treturn result, NewLTXError(\"stage-open\", tmpFilename, 0, uint64(txID), uint64(txID), fmt.Errorf(\"%w: %w\", ErrDiskFull, err))\n\t\t}\n\t\treturn result, fmt.Errorf(\"open temp ltx file: %w\", err)\n\t}\n\tdefer func() { _ = os.Remove(tmpFilename) }()\n\tdefer func() { _ = ltxFile.Close() }()\n\n\tuid, gid := internal.Fileinfo(db.fileInfo)\n\t_ = os.Chown(tmpFilename, uid, gid)\n\n\tdb.Logger.Log(ctx, internal.LevelTrace, \"encode header\",\n\t\t\"txid\", txID.String(),\n\t\t\"commit\", commit,\n\t\t\"walOffset\", info.offset,\n\t\t\"walSize\", sz,\n\t\t\"salt1\", rd.salt1,\n\t\t\"salt2\", rd.salt2)\n\n\ttimestamp := time.Now()","sourceCodeStart":2105,"sourceCodeEnd":2141,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/db.go#L2105-L2141","documentation":"Opening the temporary LTX file (<filename>.tmp) for read-write create failed with a disk-full condition, wrapped in an LTXError with code stage-open and classified as ErrDiskFull. Creation can also consume metadata blocks even if the WAL data would fit.","triggerScenarios":"db.openLTXFile(tmpFilename, O_RDWR|O_CREATE|O_TRUNC) returns ENOSPC (via isDiskFullError) while creating the temp file in the staging directory.","commonSituations":"Disk filled between the mkdir and open steps on a nearly-full volume; inode/quota exhaustion manifesting as ENOSPC; container storage limits hit during a large snapshot stage.","solutions":["Free disk space (or expand the volume) on the staging directory's filesystem","Check inode usage (df -i) and quotas, not just bytes","Move the Litestream staging directory to larger storage via config","Retry the sync after space is reclaimed; no reset required"],"exampleFix":"// before\n$ df -i /var/lib/litestream  # inodes exhausted\n// after\n$ find /var/lib/litestream/tmp -type f -delete  # or raise quota/inodes\n$ systemctl restart litestream","handlingStrategy":"fallback","validationCode":"var st syscall.Statfs_t\nif err := syscall.Statfs(stageDir, &st); err == nil {\n\tif st.Bavail*uint64(st.Bsize) < minFreeBytes || st.Ffree < minFreeInodes {\n\t\treturn errors.New(\"insufficient disk/inodes for staging\")\n\t}\n}","typeGuard":null,"tryCatchPattern":"var ltxErr *litestream.LTXError\nif errors.As(err, &ltxErr) && errors.Is(ltxErr, litestream.ErrDiskFull) && ltxErr.Code == \"stage-open\" {\n\tfreeDiskOrExpandVolume()\n\tnextSyncWillRetry() // no reset needed\n}","preventionTips":["Monitor both bytes and inodes on the staging volume (df and df -i)","Keep headroom for temp LTX files roughly the size of the WAL","Enforce disk alerts in the same runbook as replication alerts"],"tags":["disk-full","filesystem","staging"],"backgroundTag":"file-size-limit-exceeded","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}