benbjohnson/litestream · error

close txid file: %w

Error message

close txid file: %w

What it means

WriteTXIDFile wraps f.Close() as 'close txid file: %w'. Close is called after fsync to release the file descriptor for the temp file before the atomic rename. A close error means buffered metadata could not be committed or the descriptor could not be released, so the temp file may be incomplete and the TXID is NOT promoted to the final '<outputPath>-txid' path.

Source

Thrown at replica.go:1740

	tmpPath := txidPath + ".tmp"

	f, err := os.Create(tmpPath)
	if err != nil {
		return fmt.Errorf("create txid temp file: %w", err)
	}
	defer f.Close()
	defer os.Remove(tmpPath)

	if _, err := fmt.Fprintln(f, txid); err != nil {
		return fmt.Errorf("write txid: %w", err)
	}

	if err := f.Sync(); err != nil {
		return fmt.Errorf("sync txid file: %w", err)
	}

	if err := f.Close(); err != nil {
		return fmt.Errorf("close txid file: %w", err)
	}

	if err := os.Rename(tmpPath, txidPath); err != nil {
		return fmt.Errorf("rename txid file: %w", err)
	}

	if err := internal.FsyncDir(filepath.Dir(txidPath)); err != nil {
		return fmt.Errorf("sync txid dir: %w", err)
	}
	return nil
}

// ReadTXIDFile reads the TXID from a sidecar file at <outputPath>-txid.
// Returns 0, nil if the file does not exist (first run).
func ReadTXIDFile(outputPath string) (ltx.TXID, error) {
	txidPath := TXIDPath(outputPath)

	data, err := os.ReadFile(txidPath)

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Check for file-descriptor exhaustion (ulimit -n) and raise it or fix descriptor leaks in the host process
  2. Inspect system logs for device writeback errors and repair the storage volume
  3. Remove stale '<outputPath>-txid.tmp' files and retry the operation
  4. Move the output path onto a local, POSIX-compliant filesystem

Example fix

// before: ignoring close errors in your own temp-file code
f.Close()
os.Rename(tmpPath, finalPath)
// after
if err := f.Close(); err != nil {
    os.Remove(tmpPath)
    return fmt.Errorf("close temp file: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Check descriptor headroom before batch operations
if rlim := getFDLimit(); rlim < 1024 { raiseFDLimitOrWarn() }

Type guard

func fdBudgetOK() bool { return countOpenFDs() < fdLimit()-64 }

Try / catch

if err := WriteTXIDFile(outputPath, txid); err != nil {
    if errors.Is(err, syscall.EMFILE) {
        // raise ulimit -n or fix fd leaks, then retry
    }
    return err
}

Prevention

When it happens

Trigger: Calling WriteTXIDFile when closing '<outputPath>-txid.tmp' fails: delayed writeback errors surfacing at close, out-of-file-descriptors (EMFILE), or deferred f.Close() earlier in the function having already interacted oddly with the same descriptor on some platforms.

Common situations: File-descriptor exhaustion in long-running litestream processes; disk errors reported at close time after writeback; container limits (ulimit -n) too low; Windows/SMB shares returning errors on close.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/f788fa3e37e6bddc. Report an issue: GitHub.