wavetermdev/waveterm · error

required secret %q is not bound

Error message

required secret %q is not bound

What it means

BuildAppSecretEnv builds the environment variable map for an app by iterating manifest.Secrets. If a secret is declared non-optional (secretMeta.Optional == false) and the bindings map has no entry for it, the function fails fast with 'required secret %q is not bound'. The library refuses to launch apps whose required configuration is incomplete.

Source

Thrown at pkg/waveappstore/waveappstore.go:819

	return nil
}

func BuildAppSecretEnv(appId string, manifest *wshrpc.AppManifest, bindings map[string]string) (map[string]string, error) {
	if manifest == nil {
		return make(map[string]string), nil
	}

	if bindings == nil {
		bindings = make(map[string]string)
	}

	secretEnv := make(map[string]string)

	for secretName, secretMeta := range manifest.Secrets {
		boundSecretName, hasBinding := bindings[secretName]

		if !secretMeta.Optional && !hasBinding {
			return nil, fmt.Errorf("required secret %q is not bound", secretName)
		}

		if !hasBinding {
			continue
		}

		secretValue, exists, err := secretstore.GetSecret(boundSecretName)
		if err != nil {
			return nil, fmt.Errorf("failed to get secret %q: %w", boundSecretName, err)
		}

		if !exists {
			if !secretMeta.Optional {
				return nil, fmt.Errorf("required secret %q is bound to %q which does not exist in secret store", secretName, boundSecretName)
			}
			continue
		}

View on GitHub (pinned to a4447c1563)

Solutions

  1. Add a binding for the named secret via WriteAppSecretBindings (e.g. {"SECRET_NAME": "store-key"})
  2. Check the manifest's Secrets section for the exact secret name and use it verbatim (case-sensitive)
  3. If the secret is genuinely unnecessary, update/patch the manifest to mark it optional
  4. Re-read bindings with ReadAppSecretBindings to confirm what is currently bound

Example fix

// before
bindings, _ := waveappstore.ReadAppSecretBindings(appId) // missing "API_KEY"
env, err := waveappstore.BuildAppSecretEnv(appId, manifest, bindings)
// after
bindings["API_KEY"] = "myapp/api-key" // bind required secret to a store key
err := waveappstore.WriteAppSecretBindings(appId, bindings)
env, err := waveappstore.BuildAppSecretEnv(appId, manifest, bindings)
Defensive patterns

Strategy: validation

Validate before calling

bindings, err := waveappstore.ReadAppSecretBindings(appId)
if err != nil { return err }
for name, meta := range manifest.Secrets {
    if !meta.Optional {
        if _, ok := bindings[name]; !ok {
            return fmt.Errorf("precheck: required secret %s unbound", name)
        }
    }
}

Type guard

func allRequiredSecretsBound(manifest *wshrpc.AppManifest, bindings map[string]string) bool {
    for name, meta := range manifest.Secrets {
        if !meta.Optional {
            if _, ok := bindings[name]; !ok { return false }
        }
    }
    return true
}

Try / catch

env, err := waveappstore.BuildAppSecretEnv(appId, manifest, bindings)
if err != nil && strings.Contains(err.Error(), "is not bound") {
    return fmt.Errorf("app %s misconfigured: %w (run the binding command)", appId, err)
}

Prevention

When it happens

Trigger: Calling BuildAppSecretEnv (via runBuilderApp or GetStatus) with a manifest that declares a required secret while the bindings map returned by ReadAppSecretBindings lacks a key for that secret name.

Common situations: App was installed/upgraded to a version whose manifest added a new required secret; user never ran the binding command for that secret; secret name typo in the bindings file; bindings file was reset/emptied.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/9a715f300a9cbbd2. Report an issue: GitHub.