anomalyco/sst · error

failed to read file %s: %w

Error message

failed to read file %s: %w

What it means

uploadAssets resolved a manifest entry to an absolute path (directory + fileKey) and os.ReadFile failed, so the file's contents could not be base64-encoded and uploaded. The wrapped OS error in the message says whether the file is missing (no such file or directory), unreadable (permission denied), or is a directory.

Source

Thrown at pkg/server/resource/cloudflare-worker-assets.go:237

		// Find the file path for this hash in the manifest
		var fileKey string
		var contentType string
		for path, entry := range manifest {
			if entry.Hash == hash {
				fileKey = path
				contentType = entry.ContentType
				break
			}
		}
		if fileKey == "" {
			return "", fmt.Errorf("hash %s not found in manifest", hash)
		}
		
		// Read file content
		absFilePath := filepath.Join(directory, fileKey)
		fileContent, err := os.ReadFile(absFilePath)
		if err != nil {
			return "", fmt.Errorf("failed to read file %s: %w", absFilePath, err)
		}
		
		// Base64 encode the file content
		encodedContent := base64.StdEncoding.EncodeToString(fileContent)
		
		// Create form field with content type header
		part, err := writer.CreatePart(map[string][]string{
			"Content-Disposition": []string{fmt.Sprintf(`form-data; name="%s"; filename="%s"`, hash, hash)},
			"Content-Type":        []string{contentType},
		})
		if err != nil {
			return "", fmt.Errorf("failed to create form part %s: %w", hash, err)
		}

		_, err = part.Write([]byte(encodedContent))
		if err != nil {
			return "", fmt.Errorf("failed to write encoded content for %s: %w", hash, err)
		}

View on GitHub (pinned to a0bd20f762)

Solutions

  1. Check the wrapped OS error: 'no such file' means fix the directory path or rebuild assets; 'permission denied' means fix file permissions.
  2. Verify the 'directory' input matches the actual build output folder (absolute path recommended).
  3. Rebuild assets before deploy and ensure no cleanup step runs between manifest generation and upload.
  4. Check file name casing matches exactly (Linux CI filesystems are case-sensitive).
  5. Ensure the deploy runs on the same machine/container where the assets were built.

Example fix

// before: relative directory resolved from wrong cwd
Directory: "dist/client"
// after: absolute path anchored to the project root
Directory: path.resolve(__root, "dist/client")
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight: every manifest entry must exist and be readable at directory/fileKey
import fs from "fs";
import path from "path";
function validateAssetFiles(manifest, dir) {
  for (const file of Object.keys(manifest)) {
    const p = path.resolve(dir, file);
    const st = fs.statSync(p); // throws ENOENT/EACCES early with a clear path
    if (!st.isFile()) throw new Error(`not a file: ${p}`);
    fs.accessSync(p, fs.constants.R_OK);
  }
}

Try / catch

try {
  await uploadAssets(manifest, dir);
} catch (e) {
  if (String(e).startsWith("failed to read file")) {
    const p = String(e).match(/failed to read file (.*?):/)?.[1];
    throw new Error(`Asset file unreadable at ${p} - check 'directory' path, file casing, and permissions`);
  }
  throw e;
}

Prevention

When it happens

Trigger: The asset file at directory/fileKey does not exist at upload time (deleted or renamed after the manifest was built), directory points at the wrong local folder, file permissions block reading, or fileKey contains path separators that don't resolve on the current OS.

Common situations: Deploying from a machine/CI where the build output directory path differs from the configured 'directory' input; assets cleaned by a later build step before upload; case-sensitive vs case-insensitive filesystem mismatches (macOS dev vs Linux CI); permission-restricted files in the output dir.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of anomalyco/sst@a0bd20f762 (2026-08-30). Data as JSON: /api/errors/22bbd332cf8da6c7. Report an issue: GitHub.