wavetermdev/waveterm · error

offset is past the end of the file

Error message

offset is past the end of the file

What it means

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.

Source

Thrown at pkg/filestore/blockstore.go:251

		}
		entry.writeAt(0, data, true)
		// since WriteFile can *truncate* the file, we need to flush the file to the DB immediately
		return entry.flushToDB(ctx, true)
	})
}

func (s *FileStore) WriteAt(ctx context.Context, zoneId string, name string, offset int64, data []byte) error {
	if offset < 0 {
		return fmt.Errorf("offset must be non-negative")
	}
	return withLock(s, zoneId, name, func(entry *CacheEntry) error {
		err := entry.loadFileIntoCache(ctx)
		if err != nil {
			return err
		}
		file := entry.File
		if offset > file.Size {
			return fmt.Errorf("offset is past the end of the file")
		}
		partMap := file.computePartMap(offset, int64(len(data)))
		incompleteParts := incompletePartsFromMap(partMap)
		err = entry.loadDataPartsIntoCache(ctx, incompleteParts)
		if err != nil {
			return err
		}
		entry.writeAt(offset, data, false)
		return nil
	})
}

func (s *FileStore) AppendData(ctx context.Context, zoneId string, name string, data []byte) error {
	return withLock(s, zoneId, name, func(entry *CacheEntry) error {
		err := entry.loadFileIntoCache(ctx)
		if err != nil {
			return err
		}

View on GitHub (pinned to a4447c1563)

Solutions

  1. Read the current file size first (ReadAt with size 0 or a stat call) and clamp offset to <= size.
  2. To extend the file, write sequentially from the current end rather than seeking past it.
  3. Refresh any cached size before each WriteAt when multiple writers exist.

Example fix

// before
offset := lastKnownSize + 1024
fs.WriteAt(ctx, zone, name, offset, data) // may exceed current size
// after
offset := lastKnownSize
fs.WriteAt(ctx, zone, name, offset, data) // write at/within EOF
Defensive patterns

Strategy: validation

Validate before calling

size := fileSize(zone, name) // from stat or a 0-size ReadAt
if offset > size { return fmt.Errorf("offset %d beyond EOF %d", offset, size) }

Type guard

func inBounds(offset, fileSize int64) bool { return offset >= 0 && offset <= fileSize }

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/58f6b204eb18b33c. Report an issue: GitHub.