dagger/dagger · error
key %q is not set in user-level config for workspace %q
Error message
key %q is not set in user-level config for workspace %q
What it means
DeleteUserConfigValue removes a key under [workspaces.<workspaceKey>] in user-level config and then prunes emptied tables. Before deleting, it checks tree.GetPath; if the key does not exist it throws this error instead of silently succeeding, so callers can distinguish "removed" from "was never set".
Source
Thrown at core/workspace/userconfig.go:294
}
// DeleteUserConfigValue removes a config value under
// [workspaces.<workspaceKey>] in user-level config bytes, pruning any tables
// the removal leaves empty. It errors when the key is not set for that
// workspace.
func DeleteUserConfigValue(existing []byte, workspaceKey, key string) ([]byte, error) {
parts, err := userOverlayKeyParts(key)
if err != nil {
return nil, err
}
tree, entryKey, err := userConfigTreeAndEntryKey(existing, workspaceKey)
if err != nil {
return nil, err
}
fullPath := append([]string{"workspaces", entryKey}, parts...)
if tree.GetPath(fullPath) == nil {
return nil, fmt.Errorf("key %q is not set in user-level config for workspace %q", key, entryKey)
}
if err := tree.DeletePath(fullPath); err != nil {
return nil, fmt.Errorf("unset %q: %w", key, err)
}
// Prune tables the removal left empty, innermost first.
for prefix := fullPath[:len(fullPath)-1]; len(prefix) > 0; prefix = prefix[:len(prefix)-1] {
parent, ok := tree.GetPath(prefix).(*toml.Tree)
if !ok || len(parent.Keys()) > 0 {
break
}
if err := tree.DeletePath(prefix); err != nil {
return nil, fmt.Errorf("prune empty table %q: %w", strings.Join(prefix, "."), err)
}
}
out, err := tree.ToTomlString()
if err != nil {
return nil, fmt.Errorf("serialize user config: %w", err)View on GitHub (pinned to 82ba2681db)
Solutions
- Check the key exists first with GetPath at ["workspaces", entryKey, ...parts] and treat nil as already-deleted
- Verify the entryKey (workspace key) matches the one the value was written under
- Re-run with the exact key string used at write time (segments and casing must match)
- If the delete is idempotent-critical, catch this error and ignore it
Example fix
// before: unconditional delete fails on second run
if _, err := workspace.DeleteUserConfigValue(data, key, wsKey); err != nil { return err }
// after: tolerate not-set
if _, err := workspace.DeleteUserConfigValue(data, key, wsKey); err != nil && !strings.Contains(err.Error(), "is not set") { return err } Defensive patterns
Strategy: try-catch
Validate before calling
full := append([]string{"workspaces", entryKey}, parts...)
if tree.GetPath(full) == nil {
return nil // already unset; nothing to do
} Try / catch
_, err := workspace.DeleteUserConfigValue(data, key, wsKey)
if err != nil && strings.Contains(err.Error(), "is not set") {
return data, nil // treat as idempotent success
} Prevention
- Make deletes idempotent by tolerating the not-set error
- Verify the entryKey matches where the value was written
- Reuse one key-builder helper for set and unset so paths always match
- Check current value with GetPath before deleting in UI flows
When it happens
Trigger: DeleteUserConfigValue called for a key that was never written for that workspace, a typo'd key, a key belonging to a different entryKey (workspace), or after the key was already deleted in a prior call.
Common situations: Running an unset command twice; unsetting a key with a slightly different path than it was set with; switching workspaces (entryKey) and assuming settings carry over; concurrent edits where another process already removed the key.
Related errors
- key %q is missing an environment name
- key %q cannot be stored in user-level config; only modules.<
- generateModule is called but module config is missing
- load persisted %s object: %w
- load persisted %s snapshot link: %w
AI-assisted analysis of dagger/dagger@82ba2681db (2026-09-05).
Data as JSON: /api/errors/58c7f2d95fa1b931.
Report an issue: GitHub.