anomalyco/sst · error

failed to copy %s to %s: %w

Error message

failed to copy %s to %s: %w

What it means

Raised in syncPythonFiles (pkg/runtime/python/python.go:341) when copyFile fails while syncing a changed/new .py file from source into the artifact directory. copyFile wraps its own failures (mkdir, open source, create dest, io.Copy), so the cause chain tells you which low-level step failed — most often the source file was deleted mid-sync or the destination is unwritable.

Source

Thrown at pkg/runtime/python/python.go:341

	// Copy/update changed files
	for relPath, sourceInfo := range sourceFiles {
		sourcePath := filepath.Join(srcDir, relPath)
		artifactPath := filepath.Join(destDir, relPath)

		needsCopy := true
		if artifactInfo, exists := artifactFiles[relPath]; exists {
			if !sourceInfo.ModTime().After(artifactInfo.ModTime()) {
				needsCopy = false
			}
		}

		if needsCopy {
			if err := os.MkdirAll(filepath.Dir(artifactPath), 0755); err != nil {
				return fmt.Errorf("failed to create directory for %s: %v", artifactPath, err)
			}

			if err := copyFile(sourcePath, artifactPath); err != nil {
				return fmt.Errorf("failed to copy %s to %s: %w", sourcePath, artifactPath, err)
			}
		}
	}

	return nil
}

// flattenSrcLayout removes the "src/pkg" segment from paths that follow the
// PEP 517 src-layout convention (e.g., "pkg/src/pkg/module" -> "pkg/module").
// Only flattens when the directory after "src" matches the directory before it.
func flattenSrcLayout(filePath string) string {
	parts := strings.Split(filePath, "/")
	if len(parts) < 3 {
		return filePath
	}
	for i := 0; i < len(parts)-1; i++ {
		if parts[i] == "src" && i > 0 && i+1 < len(parts) && parts[i-1] == parts[i+1] {
			flattened := append([]string{}, parts[:i]...)

View on GitHub (pinned to a0bd20f762)

Solutions

  1. Read the wrapped cause: 'failed to open source file' means the source vanished — re-save/recreate the file or restart dev so the walk rescan picks current state; 'failed to create destination file'/'create directory' means fix artifact permissions or disk space
  2. Retry the operation — transient races usually resolve on the next sync trigger
  3. Normalize the environment: stop dev, remove the artifact dir, restart to force a clean full sync

Example fix

// before
failed to copy handlers/user.py to .sst/artifacts/handlers/user.py: failed to open source file: no such file or directory
// after (recreate or restore the file, then)
git checkout -- handlers/user.py
sst dev
Defensive patterns

Strategy: retry

Validate before calling

// confirm source file still exists just before copy
if _, err := os.Stat(sourcePath); err != nil {
    return fmt.Errorf("source vanished before copy: %s", sourcePath)
}

Try / catch

if err := copyFile(sourcePath, artifactPath); err != nil {
    if errors.Is(err, fs.ErrNotExist) {
        // file deleted mid-sync: rescan and retry once
        return resyncAndRetry(sourcePath, artifactPath)
    }
    if errors.Is(err, fs.ErrPermission) {
        return fixPermsAndRetry(artifactPath)
    }
    return err
}

Prevention

When it happens

Trigger: Source file deleted between the walk scan and the copy (racing editor/git operation); destination file locked or read-only; MkdirAll inside copyFile failing (disk full, permissions); io.Copy I/O error.

Common situations: Saving/deleting files rapidly during `sst dev` in workspace-layout Python projects; antivirus/indexers holding the destination file on Windows; read-only artifact dirs from container-built artifacts.

Related errors


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