pulumi/pulumi · error

unmarshalling passphrase state: %w

Error message

unmarshalling passphrase state: %w

What it means

EditDistributedState (or equivalent) for the passphrase secrets manager deserializes the persisted state JSON into localSecretsManagerState to recover the salt string for the workspace stack info. If the raw state is not valid JSON or doesn't match localSecretsManagerState's shape, this error wraps the json.Unmarshal failure.

Source

Thrown at pkg/secrets/passphrase/manager.go:127

func (sm *localSecretsManager) Decrypter() config.Decrypter {
	contract.Assertf(sm.crypter != nil, "decrypter not initialized")
	return sm.crypter
}

func (sm *localSecretsManager) Encrypter() config.Encrypter {
	contract.Assertf(sm.crypter != nil, "encrypter not initialized")
	return sm.crypter
}

func EditProjectStack(info *workspace.ProjectStack, state json.RawMessage) error {
	info.EncryptedKey = ""
	info.SecretsProvider = ""

	var s localSecretsManagerState
	err := json.Unmarshal(state, &s)
	if err != nil {
		return fmt.Errorf("unmarshalling passphrase state: %w", err)
	}
	info.EncryptionSalt = s.Salt
	return nil
}

var (
	lock  sync.Mutex
	cache map[string]secrets.Manager
)

// clearCachedSecretsManagers is used to clear the cache, for tests.
func clearCachedSecretsManagers() {
	lock.Lock()
	defer lock.Unlock()
	cache = nil
}

// getCachedSecretsManager returns a cached secret manager and true, or nil and false if not in the cache.

View on GitHub (pinned to 793f7b2e16)

Solutions

  1. Restore Pulumi.<stack>.yaml from version control or a backup.
  2. Confirm the state is JSON of the form {"salt":"v1:<base64>:<base64>"} and fix its shape.
  3. If unrecoverable, re-initialize the stack's secrets provider and re-enter secret values.
  4. Route the state to the matching provider's edit function (cloud state should go to the cloud manager).

Example fix

// before
EditProjectStack(info, json.RawMessage("v1:abc")) // raw salt text, not JSON

// after
EditProjectStack(info, json.RawMessage(`{"salt":"v1:abc:def=="}`))
Defensive patterns

Strategy: validation

Validate before calling

func isValidPassphraseState(state json.RawMessage) bool {
    var probe struct {
        Salt string `json:"salt"`
    }
    return json.Unmarshal(state, &probe) == nil && strings.HasPrefix(probe.Salt, "v1:")
}
// call EditProjectStack only if isValidPassphraseState(state)

Type guard

func asPassphraseState(state json.RawMessage) (*localSecretsManagerState, bool) {
    var s localSecretsManagerState
    if err := json.Unmarshal(state, &s); err != nil || !strings.HasPrefix(s.Salt, "v1:") {
        return nil, false
    }
    return &s, true
}

Try / catch

if err := EditProjectStack(info, state); err != nil {
    if strings.Contains(err.Error(), "unmarshalling passphrase state") {
        // restore config from version control or re-init secrets provider
    }
    return err
}

Prevention

When it happens

Trigger: Calling the passphrase manager's edit-state function with a corrupt, truncated, or wrong-provider state blob (e.g. a cloud manager state JSON or plain salt text instead of {"salt":"v1:..."}).

Common situations: Hand-edited Pulumi.<stack>.yaml; git merge conflicts in stack config; copying state between stacks with different secrets providers; truncated file writes.

Related errors


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