pulumi/pulumi · error

resolving pulumi cwd: %w

Error message

resolving pulumi cwd: %w

What it means

NewPulumi canonicalizes the given cwd via canonicalRoot before storing it; if that fails (path cannot be made absolute/resolved, e.g. nonexistent directory or unreadable ancestor), the error is wrapped as 'resolving pulumi cwd: %w'. The Pulumi tool refuses to construct with an unusable working directory since all subsequent operations depend on it.

Source

Thrown at pkg/cmd/pulumi/neo/tools/pulumi.go:115

	OnDiag func(toolName, severity, message, urn string)
	// OnEnd finalizes the block. err is empty on success, otherwise the
	// wrapped engine error string. counts is the typed ResourceChanges map
	// from the engine.
	OnEnd func(toolName, err string, counts display.ResourceChanges, elapsed string)
}

// NewPulumi creates a Pulumi handler sandboxed under cwd. The workspace is captured
// at construction so tests can inject a fake; the backend is resolved fresh on each
// tool call (see run). Sink may be nil when running outside the interactive TUI
// (non-interactive mode); in that case progress is silently dropped and the final
// result is still returned to the caller.
func NewPulumi(cwd string, ws pkgWorkspace.Context, sink *PulumiSink) (*Pulumi, error) {
	if ws == nil {
		return nil, errors.New("workspace is required")
	}
	abs, err := canonicalRoot(cwd)
	if err != nil {
		return nil, fmt.Errorf("resolving pulumi cwd: %w", err)
	}
	return &Pulumi{Cwd: abs, Workspace: ws, Sink: sink}, nil
}

// pulumiArgs matches pulumi-service:cmd/agents/src/agents_py/mcp/pulumi_mcp.py's
// pulumi_preview/pulumi_up parameters.
type pulumiArgs struct {
	ProjectName          string            `json:"project_name"`
	StackName            string            `json:"stack_name"`
	LocalPulumiDir       string            `json:"local_pulumi_dir"`
	EnvironmentVariables map[string]envVal `json:"environment_variables,omitempty"`
}

// envVal decodes the dict value type `str | SecretValue` used by the upstream schema.
// Plain and Secret are mutually exclusive; the Value() accessor returns whichever is set.
// Secret values must never be echoed into logs, progress messages, or the events file.
type envVal struct {
	Plain  string

View on GitHub (pinned to 793f7b2e16)

Solutions

  1. Verify the cwd exists and is a directory before calling NewPulumi (os.Stat).
  2. Update the configured project directory to the current actual location of the workspace.
  3. Pass an absolute, existing path; resolve symlinks yourself if the workspace path is a link.
  4. Fix permissions on ancestor directories so canonicalRoot can traverse them.

Example fix

// before
p, err := NewPulumi("/old/deleted/project", ws, sink)
// after
if _, err := os.Stat("/current/project"); err == nil {
    p, err := NewPulumi("/current/project", ws, sink)
}
Defensive patterns

Strategy: validation

Validate before calling

fi, err := os.Stat(cwd)
if err != nil {
    return fmt.Errorf("cwd %s does not exist: %w", cwd, err)
}
if !fi.IsDir() {
    return fmt.Errorf("cwd %s is not a directory", cwd)
}

Try / catch

p, err := NewPulumi(cwd, ws, sink)
if err != nil {
    if strings.HasPrefix(err.Error(), "resolving pulumi cwd:") {
        return fmt.Errorf("check configured project directory %q: %w", cwd, err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling NewPulumi with a cwd that does not exist, is a file rather than a directory, has a broken symlink ancestor, or cannot be canonicalized due to permissions.

Common situations: Passing a project directory path from stale configuration after the directory was moved/deleted; symlinked workspaces whose target vanished; containers started with a workdir that was replaced at runtime; relative cwd strings while the process cwd is invalid.

Related errors


AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31). Data as JSON: /api/errors/ab4275e327bc7277. Report an issue: GitHub.