{"record":{"id":"1935287f36c814fd","repo":"jdx/mise","slug":"bootstrap-user-requires-undeclared-group-gr","errorCode":null,"errorMessage":"bootstrap user '{}' requires undeclared group '{group}'","messagePattern":"bootstrap user '(.+?)' requires undeclared group '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/accounts.rs","lineNumber":613,"sourceCode":"        .iter()\n        .map(|group| (group.name.as_str(), group.state))\n        .collect::<IndexMap<_, _>>();\n    for user in users\n        .iter()\n        .filter(|user| user.state == AccountState::Present)\n    {\n        for group in user\n            .group\n            .iter()\n            .chain(user.groups.iter().flat_map(|groups| groups.iter()))\n        {\n            match managed_groups.get(group.as_str()) {\n                Some(AccountState::Absent) => bail!(\n                    \"bootstrap user '{}' requires group '{group}', but that group is absent\",\n                    user.name\n                ),\n                Some(AccountState::Present) => {}\n                None if nix::unistd::Group::from_name(group)?.is_none() => bail!(\n                    \"bootstrap user '{}' requires undeclared group '{group}'\",\n                    user.name\n                ),\n                None => {}\n            }\n        }\n    }\n    Ok(())\n}\n\nfn inspect_user(name: &str, desired_uid: Option<u32>) -> Result<UserInspection> {\n    let Some(user) = nix::unistd::User::from_name(name)? else {\n        return match desired_uid {\n            Some(uid) => match nix::unistd::User::from_uid(nix::unistd::Uid::from_raw(uid))? {\n                Some(user) => Ok(UserInspection::IdCollision {\n                    uid,\n                    name: user.name,\n                }),","sourceCodeStart":595,"sourceCodeEnd":631,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/accounts.rs#L595-L631","documentation":"The other arm of the group-reference check: a present user references a group that is not managed in [bootstrap.groups] (absent from the managed map) and also does not exist on the system (nix::unistd::Group::from_name returns None). mise would otherwise create a user pointing at a nonexistent primary/supplementary group, so validation fails and names both the user and the undeclared group.","triggerScenarios":"A present user with group = \"release\" or groups = [\"release\"] where [bootstrap.groups.release] is not defined in any config layer and the host has no 'release' group in /etc/group. Managed groups set to present pass; existing system groups (e.g. \"users\") also pass.","commonSituations":"Typos in group names; assuming a distro group exists on a minimal image; forgetting to declare in config a group that only exists on some hosts; fresh containers where the base image lacks the expected group.","solutions":["Declare the group in config: add [bootstrap.groups.<name>] with state = \"present\" so mise creates it before the user.","If the group should already exist on the host, fix the host (create it) or fix the typo in the user's group/groups reference.","Use an existing system group intentionally (e.g. \"users\") if that is the correct parent."],"exampleFix":"# before\n[bootstrap.users.deploy]\nstate = \"present\"\ngroup = \"deploy\"   # no such group on host or in config\n# after\n[bootstrap.users.deploy]\nstate = \"present\"\ngroup = \"deploy\"\n\n[bootstrap.groups.deploy]\nstate = \"present\"","handlingStrategy":"validation","validationCode":"#!/usr/bin/env bash\n# every group referenced by a present user must be managed or already exist\nwhile read -r g; do\n  grep -q \"^\\[bootstrap\\.groups\\.$g\\]\" mise.toml 2>/dev/null || getent group \"$g\" >/dev/null || {\n    echo \"undeclared group: $g\" >&2; exit 1;\n  }\ndone < <(awk '/^\\[bootstrap\\.users\\./{u=1} /^group = /{if(u) print $3} /^\\[/{u=0}' mise.toml | tr -d '\"')","typeGuard":"import grp\n\ndef group_exists(name: str) -> bool:\n    try:\n        grp.getgrnam(name)\n        return True\n    except KeyError:\n        return False\n\ndef user_groups_available(user: dict, managed: set, exists=group_exists) -> bool:\n    refs = ([user['group']] if 'group' in user else []) + list(user.get('groups', []))\n    return all(g in managed or exists(g) for g in refs)","tryCatchPattern":null,"preventionTips":["Declare every group mise should own under [bootstrap.groups], including ones that exist on some hosts only.","Spell-check group references; they are validated literally.","On minimal containers, don't assume distro groups exist — declare them."],"tags":["mise","bootstrap","users","groups","undeclared-dependency","config-validation"],"backgroundTag":"undeclared-dependency","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}