{"record":{"id":"4b3b914d3a900e3e","repo":"jdx/mise","slug":"has-unsaved-directory-permission-changes-save","errorCode":null,"errorMessage":"{} has unsaved directory permission changes; save them before pulling. Sharing is paused","messagePattern":"(.+?) has unsaved directory permission changes; save them before pulling\\. Sharing is paused","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/sync/directories.rs","lineNumber":82,"sourceCode":"        let path = roots.locate(portable).path().unwrap().to_path_buf();\n        let before = observe(&path)?;\n        let desired = tracked\n            .manifest\n            .permissions\n            .get(portable)\n            .copied()\n            .unwrap_or(0o755);\n        let was_directory = local\n            .as_deref()\n            .map(|head| repo.object_at(head, portable))\n            .transpose()?\n            .flatten()\n            .is_some_and(|(mode, _)| mode == \"040000\");\n        if was_directory\n            && before.map(|(_, _, bits)| bits)\n                != Some(saved.permissions.get(portable).copied().unwrap_or(0o755))\n        {\n            bail!(\n                \"{} has unsaved directory permission changes; save them before pulling. Sharing is paused\",\n                crate::file::display_path(&path)\n            );\n        }\n        if before.map(|(_, _, bits)| bits) != Some(desired) {\n            steps.push(Step {\n                path,\n                before,\n                desired,\n                written: None,\n            });\n        }\n    }\n    steps.sort_by_key(|step| step.path.components().count());\n    Ok(steps)\n}\n\nimpl Step {","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/sync/directories.rs#L64-L100","documentation":"During directory permission planning, if a directory currently has permission bits different from those saved in history, the planner treats the change as unsaved local work. Pulling would clobber it, so the operation aborts and sharing is paused until the user saves (commits) the permission change.","triggerScenarios":"Directory mode on disk differs from the saved permission entry (default 0o755 when nothing saved) for a tracked directory at plan time.","commonSituations":"A user ran chmod on a config directory without recording it in setup history; a package manager or tool tightened/loosened directory perms; restoring from backup with different umask.","solutions":["Save the current permission change into setup history (commit it), then re-run the pull.","Revert the chmod back to the recorded mode (`chmod 755 <dir>` unless a different mode was saved).","Re-run pull after reconciling; sharing resumes automatically."],"exampleFix":"// before\nchmod 700 ~/.config/app   # unsaved\n// after\nchmod 700 ~/.config/app && mise history save   # record before pulling","handlingStrategy":"validation","validationCode":"let bits = dir_mode(&path)?;\nlet saved = saved_permissions.get(&path).copied().unwrap_or(0o755);\nif bits != saved {\n    // commit/revert the permission change before pulling\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make directory permission changes through setup-history so they are recorded","Compare current modes to saved ones (`stat -c %a`) before pulling","Avoid ad-hoc chmod on managed directories"],"tags":["permissions","directory","sync","conflict"],"backgroundTag":"invalid-state-transition","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}