{"record":{"id":"64cf1c411aa87362","repo":"vitest-dev/vitest","slug":"expected-string-coverage-payload-received-typeo","errorCode":null,"errorMessage":"Expected string coverage payload, received ${typeof coverage}, ${JSON.stringify(coverage)}","messagePattern":"Expected string coverage payload, received (.+?), (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/node/coverage.ts","lineNumber":293,"sourceCode":"      await fs.rm(this.coverageFilesDirectory, {\n        recursive: true,\n        force: true,\n        maxRetries: 10,\n      })\n    }\n\n    await fs.mkdir(this.coverageFilesDirectory, { recursive: true })\n\n    this.coverageFiles = new Map()\n  }\n\n  onAfterSuiteRun({ coverage, environment, projectName, testFiles }: AfterSuiteRunMeta): void {\n    if (!coverage) {\n      return\n    }\n\n    if (typeof coverage !== 'string') {\n      throw new TypeError(`Expected string coverage payload, received ${typeof coverage}, ${JSON.stringify(coverage)}`)\n    }\n    const filename = coverage\n\n    let entry = this.coverageFiles.get(projectName || DEFAULT_PROJECT)\n\n    if (!entry) {\n      entry = {}\n      this.coverageFiles.set(projectName || DEFAULT_PROJECT, entry)\n    }\n\n    const testFilenames = testFiles.join()\n    entry[environment] ??= {}\n    // If there's a result from previous run, overwrite it\n    entry[environment][testFilenames] = filename\n  }\n\n  async readCoverageFiles<CoverageType>({ onFileRead, onFinished, onDebug }: {\n    /** Callback invoked with a single coverage result */","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/vitest-dev/vitest/blob/1fa9837ec26533512fdcad8baebf249771bd340a/packages/vitest/src/node/coverage.ts#L275-L311","documentation":"In the blob/coverage flow, onAfterSuiteRun expects the coverage payload to be a string (a path to a serialized coverage artifact written by the worker). If a non-string is supplied (raw object, number, etc.), it throws TypeError. This enforces the worker-to-server coverage contract.","triggerScenarios":"A worker/environment passes a coverage value that is not a string path — e.g. a raw istanbul/v8 coverage object instead of a file path — to onAfterSuiteRun. Common with mismatched coverage provider versions or a custom provider returning the wrong shape.","commonSituations":"Custom coverage provider returning an object instead of writing a file and returning its path; version skew between @vitest/* packages; middleware transforming the payload.","solutions":["Make the coverage provider serialize coverage to a file and return its path string.","Upgrade all @vitest/* packages to matching versions to align the payload contract.","If you implemented a custom provider, write coverage to disk under coverageFilesDirectory and pass the filename."],"exampleFix":"// before - worker returns raw coverage object\nreturn { coverage: coverageMap }\n\n// after - write file, return path string\nconst file = path.join(dir, `${env}-${Date.now()}.json`)\nawait fs.writeFile(file, JSON.stringify(coverageMap))\nreturn { coverage: file }","handlingStrategy":"type-guard","validationCode":"function isValidCoveragePayload(coverage) {\n  return coverage == null || typeof coverage === 'string'\n}\n// in worker: if (!isValidCoveragePayload(payload.coverage)) write coverage to file and pass path","typeGuard":"function isCoveragePath(coverage): coverage is string {\n  return typeof coverage === 'string' && coverage.length > 0\n}","tryCatchPattern":null,"preventionTips":["Keep all @vitest/* packages at the same version to preserve the payload contract.","Custom coverage providers must serialize to a file and return the path string.","Unit-test the worker-to-server coverage payload shape in isolation."],"tags":["coverage","type-mismatch","worker-payload"],"backgroundTag":null,"analyzedSha":"1fa9837ec26533512fdcad8baebf249771bd340a","analyzedAt":"2026-08-11T16:11:39.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}