{"record":{"id":"dfdc7556ed755050","repo":"wavetermdev/waveterm","slug":"circular-file-must-have-a-max-size","errorCode":null,"errorMessage":"circular file must have a max size","messagePattern":"circular file must have a max size","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/filestore/blockstore.go","lineNumber":120,"sourceCode":"\nfunc (WaveFile) UseDBMap() {}\n\ntype FileData struct {\n\tZoneId  string `json:\"zoneid\"`\n\tName    string `json:\"name\"`\n\tPartIdx int    `json:\"partidx\"`\n\tData    []byte `json:\"data\"`\n}\n\nfunc (FileData) UseDBMap() {}\n\n// synchronous (does not interact with the cache)\nfunc (s *FileStore) MakeFile(ctx context.Context, zoneId string, name string, meta wshrpc.FileMeta, opts wshrpc.FileOpts) error {\n\tif opts.MaxSize < 0 {\n\t\treturn fmt.Errorf(\"max size must be non-negative\")\n\t}\n\tif opts.Circular && opts.MaxSize <= 0 {\n\t\treturn fmt.Errorf(\"circular file must have a max size\")\n\t}\n\tif opts.Circular && opts.IJson {\n\t\treturn fmt.Errorf(\"circular file cannot be ijson\")\n\t}\n\tif opts.Circular {\n\t\tif opts.MaxSize%partDataSize != 0 {\n\t\t\topts.MaxSize = (opts.MaxSize/partDataSize + 1) * partDataSize\n\t\t}\n\t}\n\tif opts.IJsonBudget > 0 && !opts.IJson {\n\t\treturn fmt.Errorf(\"ijson budget requires ijson\")\n\t}\n\tif opts.IJsonBudget < 0 {\n\t\treturn fmt.Errorf(\"ijson budget must be non-negative\")\n\t}\n\treturn withLock(s, zoneId, name, func(entry *CacheEntry) error {\n\t\tif entry.File != nil {\n\t\t\treturn fs.ErrExist","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/filestore/blockstore.go#L102-L138","documentation":"A circular file is a fixed-size ring buffer of parts, so it requires a positive MaxSize to size the ring. MakeFile rejects Circular=true combined with MaxSize <= 0 because there would be no space to write any data.","triggerScenarios":"Calling MakeFile with opts.Circular = true and opts.MaxSize == 0 or < 0 — typically forgetting to set MaxSize at all (zero value) when requesting a circular file.","commonSituations":"Constructing FileOpts for a log-style circular file and leaving MaxSize unset (Go zero value 0); copying opts from a non-circular use case where MaxSize was intentionally 0; config key for max size missing so it defaults to 0.","solutions":["Set a positive MaxSize when Circular is true, ideally a multiple of partDataSize (it will be rounded up automatically).","Guard the call site: only set Circular=true after validating MaxSize > 0.","If the file should be unbounded, do not use Circular — omit it."],"exampleFix":"// before\nopts := wshrpc.FileOpts{Circular: true} // MaxSize left at 0\n// after\nopts := wshrpc.FileOpts{Circular: true, MaxSize: 4 * 1024 * 1024}","handlingStrategy":"validation","validationCode":"func validCircularOpts(opts wshrpc.FileOpts) bool {\n\treturn !opts.Circular || opts.MaxSize > 0\n}\nif !validCircularOpts(opts) {\n\treturn fmt.Errorf(\"circular requires MaxSize > 0\")\n}","typeGuard":null,"tryCatchPattern":"if err := store.MakeFile(ctx, zoneId, name, meta, opts); err != nil {\n\tif strings.Contains(err.Error(), \"circular file must have a max size\") {\n\t\topts.MaxSize = defaultCircularMaxSize // e.g. 4MB\n\t\terr = store.MakeFile(ctx, zoneId, name, meta, opts)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n}","preventionTips":["Always set MaxSize explicitly when Circular is true.","Centralize FileOpts construction so circular defaults are applied in one place.","Never rely on the Go zero value (0) for a circular file's MaxSize.","Require the max-size config key when circular mode is enabled."],"tags":["validation","filestore","circular-buffer"],"backgroundTag":"invalid-argument-value","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}