anomalyco/sst · error

handler not found: %s (searched %d candidate paths)

Error message

handler not found: %s (searched %d candidate paths)

What it means

findPythonFile iterates every generated candidate path (project root, plus src/, app/, functions/, lambda/, handlers/, lib/ subdirectories, with and without the .py extension) and throws this when none is a regular .py file. The message reports the handler string and how many candidate paths were searched, aiding diagnosis of where the resolver looked.

Source

Thrown at pkg/runtime/python/project.go:97

	return info, nil
}

// findPythonFile locates the Python file for the given handler path.
func findPythonFile(projectRoot, handlerPath string) (string, error) {
	filePath := extractFilePath(handlerPath)
	candidates := generateCandidatePaths(projectRoot, filePath)

	for _, candidate := range candidates {
		if info, err := os.Stat(candidate); err == nil && info.Mode().IsRegular() && strings.HasSuffix(candidate, ".py") {
			absPath, err := filepath.Abs(candidate)
			if err != nil {
				return "", fmt.Errorf("failed to get absolute path for %s: %w", candidate, err)
			}
			return absPath, nil
		}
	}

	return "", fmt.Errorf("handler not found: %s (searched %d candidate paths)", handlerPath, len(candidates))
}

// extractFilePath extracts the file path from a handler path.
func extractFilePath(handlerPath string) string {
	if lastDot := strings.LastIndex(handlerPath, "."); lastDot != -1 {
		return handlerPath[:lastDot]
	}
	return handlerPath
}

// generateCandidatePaths creates a list of potential file locations.
func generateCandidatePaths(projectRoot, handlerPath string) []string {
	var candidates []string

	// Direct path and with .py extension
	candidates = append(candidates, filepath.Join(projectRoot, handlerPath))
	if !strings.HasSuffix(handlerPath, ".py") {
		candidates = append(candidates, filepath.Join(projectRoot, handlerPath+".py"))

View on GitHub (pinned to a0bd20f762)

Solutions

  1. Confirm the exact file path (without .handler suffix) exists, e.g. ls <projectRoot>/src/index.py.
  2. Fix the handler field to a path relative to the project root, e.g. "src/index.handler".
  3. If the code lives in a non-standard directory, either move it into root/src/ or place a pyproject.toml and align paths.
  4. Check that the deploy root (resolved from cfgPath) is the directory containing your code, not a parent or child.

Example fix

// before
handler: "functions/process.fn"
// (no functions/process.py on disk)

// after
handler: "src/process.fn"  // src/process.py exists
Defensive patterns

Strategy: validation

Validate before calling

// shell
test -f <root>/src/index.py && echo OK || echo "handler file missing"
// generic check: file must exist under root or root/{src,app,functions,lambda,handlers,lib}

Try / catch

try { await deploy() } catch (e) {
  if (String(e).includes("handler not found:"))
    console.error("Searched candidate dirs; confirm file exists:", String(e));
  throw e;
}

Prevention

When it happens

Trigger: resolveHandler was given a handler path like "src/index.handler" but no file matching src/index (or src/index.py) exists under the resolved projectRoot; also fires for empty/incorrect handler values that still yield candidates.

Common situations: Renamed or deleted the handler file after updating code; handler path includes a leading './' or absolute path the candidate generator doesn't expect; monorepo where the Python app lives in a subdirectory not covered by the candidate list; handler uses underscore/hyphen inconsistently with the file name.

Related errors


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