CherryHQ/cherry-studio · error · Error

${packageLabel} file not found

Error message

${packageLabel} file not found

What it means

Thrown near the top of uploadPackage's try block when fs.existsSync(filePath) returns false. uploadPackage is the shared extract/install path invoked after uploadFromBuffer stages the buffer to a temp file; it is also the natural place a direct file-path install would start. The check is a precondition guard before StreamZip opens the file.

Source

Thrown at src/main/ai/mcp/McpPackageService.ts:512

      await fs.promises.writeFile(tempPath, fileData)
      return await this.uploadPackage(tempPath, packageFormat)
    } catch (error) {
      logger.error(`${packageLabel} upload error:`, error as Error)
      return {
        success: false,
        error: error instanceof Error ? error.message : `Failed to upload ${packageLabel} file`
      }
    }
  }

  private async uploadPackage(filePath: string, packageFormat: McpPackageFormat): Promise<McpPackageUploadResult> {
    const packageLabel = packageFormat === 'mcpb' ? 'MCPB' : 'DXT'
    const tempExtractDir = path.join(this.tempDir, `${packageFormat}_${uuidv4()}`)

    try {
      // Validate file exists
      if (!fs.existsSync(filePath)) {
        throw new Error(`${packageLabel} file not found`)
      }

      // Extract the package file (which is a ZIP archive) to a temporary directory
      logger.debug(`Extracting ${packageLabel} file: ${filePath}`)

      const zip = new StreamZip.async({ file: filePath })
      try {
        // Reject any zip-slip entry before writing anything to disk.
        assertZipEntriesWithin(Object.keys(await zip.entries()), tempExtractDir)
        await zip.extract(null, tempExtractDir)
      } finally {
        await zip.close()
      }

      // Read and validate the manifest.json
      const manifestPath = path.join(tempExtractDir, 'manifest.json')
      if (!fs.existsSync(manifestPath)) {
        throw new Error(`manifest.json not found in ${packageLabel} file`)

View on GitHub (pinned to 726446b54c)

Solutions

  1. Avoid calling service.cleanup() while uploads are in flight; ensure the lifecycle stops the service only after pending uploads complete.
  2. If the temp dir is being cleared by an external process, move the temp dir (application.getPath('feature.dxt.uploads.temp')) to a location not scanned by AV/sync.
  3. Retry the upload; if it fails again, inspect main-process logs for concurrent cleanup or shutdown around the same timestamp.
Defensive patterns

Strategy: try-catch

Validate before calling

import * as fs from 'fs'
function fileExistsAtPath(filePath: string): boolean {
  return fs.existsSync(filePath)
}

Try / catch

// uploadFromBuffer/uploadPackage already wraps this in try/catch and returns { success: false, error }.
// Callers of uploadDxt/uploadMcpb should check the result shape, not await a throw.
const result = await mcpPackageService.uploadDxt(buf, name)
if (!result.success) {
  notifyUser(result.error ?? 'Upload failed')
}

Prevention

When it happens

Trigger: The temp file written by uploadFromBuffer was removed before uploadPackage ran (race with another cleanup, antivirus quarantine, the service cleanup() running concurrently); or uploadPackage was called directly with a path that does not exist.

Common situations: Concurrent calls to McpPackageService.cleanup() (e.g. on app shutdown) deleted the temp dir mid-upload; an antivirus or sync tool quarantined the freshly written temp file; a developer invoked uploadPackage in a test with a non-existent path.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/68ba0957874d829e. Report an issue: GitHub.