{"record":{"id":"58f6b204eb18b33c","repo":"wavetermdev/waveterm","slug":"offset-is-past-the-end-of-the-file","errorCode":null,"errorMessage":"offset is past the end of the file","messagePattern":"offset is past the end of the file","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/filestore/blockstore.go","lineNumber":251,"sourceCode":"\t\t}\n\t\tentry.writeAt(0, data, true)\n\t\t// since WriteFile can *truncate* the file, we need to flush the file to the DB immediately\n\t\treturn entry.flushToDB(ctx, true)\n\t})\n}\n\nfunc (s *FileStore) WriteAt(ctx context.Context, zoneId string, name string, offset int64, data []byte) error {\n\tif offset < 0 {\n\t\treturn fmt.Errorf(\"offset must be non-negative\")\n\t}\n\treturn withLock(s, zoneId, name, func(entry *CacheEntry) error {\n\t\terr := entry.loadFileIntoCache(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfile := entry.File\n\t\tif offset > file.Size {\n\t\t\treturn fmt.Errorf(\"offset is past the end of the file\")\n\t\t}\n\t\tpartMap := file.computePartMap(offset, int64(len(data)))\n\t\tincompleteParts := incompletePartsFromMap(partMap)\n\t\terr = entry.loadDataPartsIntoCache(ctx, incompleteParts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tentry.writeAt(offset, data, false)\n\t\treturn nil\n\t})\n}\n\nfunc (s *FileStore) AppendData(ctx context.Context, zoneId string, name string, data []byte) error {\n\treturn withLock(s, zoneId, name, func(entry *CacheEntry) error {\n\t\terr := entry.loadFileIntoCache(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/filestore/blockstore.go#L233-L269","documentation":"WriteAt only allows writing within or exactly at the end of the existing file. If offset is strictly greater than the file's current Size, the write would create a hole, which the block/part model does not support, so it is rejected after loading the file into cache.","triggerScenarios":"Calling WriteAt with an offset larger than entry.File.Size — e.g. writing past EOF, using a stale cached size after another writer truncated/rewrote the file, or seeking-based logic that assumes sparse writes are allowed.","commonSituations":"Concurrent writers where one process shrinks the file while another uses a previously computed offset; resuming a write after the file was recreated smaller; porting code that assumed sparse/seek-past-EOF writes (like os.File.WriteAt on a sparse file).","solutions":["Read the current file size first (ReadAt with size 0 or a stat call) and clamp offset to <= size.","To extend the file, write sequentially from the current end rather than seeking past it.","Refresh any cached size before each WriteAt when multiple writers exist."],"exampleFix":"// before\noffset := lastKnownSize + 1024\nfs.WriteAt(ctx, zone, name, offset, data) // may exceed current size\n// after\noffset := lastKnownSize\nfs.WriteAt(ctx, zone, name, offset, data) // write at/within EOF","handlingStrategy":"validation","validationCode":"size := fileSize(zone, name) // from stat or a 0-size ReadAt\nif offset > size { return fmt.Errorf(\"offset %d beyond EOF %d\", offset, size) }","typeGuard":"func inBounds(offset, fileSize int64) bool { return offset >= 0 && offset <= fileSize }","tryCatchPattern":null,"preventionTips":["Always refresh the file size before writing; never trust stale cached sizes with concurrent writers.","Extend files sequentially from EOF instead of seeking past it.","Detect the specific message 'offset is past the end of the file' and re-stat then retry."],"tags":["filestore","bounds","offset"],"backgroundTag":"offset-out-of-range","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}