vitest-dev/vitest · error · TypeError

Expected string coverage payload, received ${typeof coverage

Error message

Expected string coverage payload, received ${typeof coverage}, ${JSON.stringify(coverage)}

What it means

BlobReporter.onAfterSuiteRun (coverage.ts:293) requires the worker's coverage payload to be a string — the path to the coverage file the worker wrote. A non-string (object, etc.) means the IPC/transport contract is broken; vitest won't try to interpret arbitrary coverage objects here.

Source

Thrown at packages/vitest/src/node/coverage.ts:293

      await fs.rm(this.coverageFilesDirectory, {
        recursive: true,
        force: true,
        maxRetries: 10,
      })
    }

    await fs.mkdir(this.coverageFilesDirectory, { recursive: true })

    this.coverageFiles = new Map()
  }

  onAfterSuiteRun({ coverage, environment, projectName, testFiles }: AfterSuiteRunMeta): void {
    if (!coverage) {
      return
    }

    if (typeof coverage !== 'string') {
      throw new TypeError(`Expected string coverage payload, received ${typeof coverage}, ${JSON.stringify(coverage)}`)
    }
    const filename = coverage

    let entry = this.coverageFiles.get(projectName || DEFAULT_PROJECT)

    if (!entry) {
      entry = {}
      this.coverageFiles.set(projectName || DEFAULT_PROJECT, entry)
    }

    const testFilenames = testFiles.join()
    entry[environment] ??= {}
    // If there's a result from previous run, overwrite it
    entry[environment][testFilenames] = filename
  }

  async readCoverageFiles<CoverageType>({ onFileRead, onFinished, onDebug }: {
    /** Callback invoked with a single coverage result */

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Ensure worker code writes coverage to a file and sends only the file path string as coverage.
  2. Use vitest's standard coverage transport (don't short-circuit onAfterSuiteRun).
  3. If writing a custom pool, mirror how @vitest/coverage-* workers serialize coverage.

Example fix

// before (custom worker)
child.send({ coverage: coverageJsonObject })

// after
const path = await writeCoverageFile(coverageJsonObject)
child.send({ coverage: path })
Defensive patterns

Strategy: validation

Validate before calling

function assertCoverageString(payload: unknown): asserts payload is string {
  if (typeof payload !== 'string') {
    throw new TypeError(`coverage must be a file-path string, got ${typeof payload}`)
  }
}

Type guard

function isCoveragePath(c: unknown): c is string {
  return typeof c === 'string' && c.length > 0
}

Prevention

When it happens

Trigger: A custom pool/environment/transport sending coverage as a structured object instead of a file path string; tampered or malformed worker IPC payload reaching onAfterSuiteRun.

Common situations: Custom forks/tinypool implementations that bypass the standard coverage serialization; tests that mock worker messages incorrectly.

Related errors


AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03). Data as JSON: /data/errors/64cf1c411aa87362.json. Report an issue: GitHub.