{"record":{"id":"d339c82531139bb7","repo":"jdx/mise","slug":"package-hook-returned-invalid-state-for","errorCode":null,"errorMessage":"{} package hook returned invalid state '{}' for '{}'","messagePattern":"(.+?) package hook returned invalid state '(.+?)' for '(.+?)'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/plugin.rs","lineNumber":563,"sourceCode":"            .packages\n            .into_iter()\n            .map(|pkg| (pkg.name.clone(), pkg))\n            .collect();\n        pkgs.iter()\n            .map(|request| {\n                let returned = by_name.get(&request.name);\n                let state = match returned {\n                    Some(pkg) if pkg.state == \"installed\" => {\n                        let installed = pkg.version.clone().unwrap_or_default();\n                        match &request.version {\n                            Some(requested) if requested != &installed => {\n                                PackageState::VersionMismatch { installed }\n                            }\n                            _ => PackageState::Installed { version: installed },\n                        }\n                    }\n                    Some(pkg) if pkg.state == \"missing\" => PackageState::Missing,\n                    Some(pkg) => bail!(\n                        \"{} package hook returned invalid state '{}' for '{}'\",\n                        self.name,\n                        pkg.state,\n                        request.name\n                    ),\n                    None => bail!(\n                        \"{} package hook did not return state for '{}'\",\n                        self.name,\n                        request.name\n                    ),\n                };\n                Ok(PackageStatus {\n                    request: request.clone(),\n                    state,\n                })\n            })\n            .collect()\n    }","sourceCodeStart":545,"sourceCodeEnd":581,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/plugin.rs#L545-L581","documentation":"Package plugins report each package's status through a Lua hook that returns entries with a `state` field from a fixed vocabulary (e.g. 'installed', 'missing', plus structured variants). When the hook returns an entry for a package with a `state` string mise does not recognize, `installed` bails out naming the plugin, the invalid state, and the package. This catches plugin/contract drift early instead of mapping unknown states to a wrong default.","triggerScenarios":"Calling `installed` (via prune_plan, apply_prune_plan, install, or upgrade) where the plugin's status hook returns a package entry whose `state` is a non-empty string outside the known set — typically due to a typo or a plugin written against a newer/older hook contract.","commonSituations":"A hand-edited or community Lua hook returns e.g. 'instaled' or 'unknown'; the plugin was written for a different mise version whose state vocabulary differed; a plugin returns version-incompatible state strings after a mise upgrade.","solutions":["Fix the plugin's hook to return only recognized state strings ('installed', 'missing', or the structured variants documented for the hook contract).","Update the plugin to the version matching your mise release, or update mise to match the plugin's contract.","Reproduce the hook manually (run the status hook for the failing package) and inspect the raw `state` value it emits.","File a bug against the plugin if it claims compatibility with your mise version."],"exampleFix":"-- before (package status hook)\nreturn { { name = 'ripgrep', state = 'present' } }\n\n-- after\nreturn { { name = 'ripgrep', state = 'installed', version = '14.1.0' } }","handlingStrategy":"type-guard","validationCode":"// Validate hook output states against the known vocabulary before feeding mise\nconst VALID_STATES = new Set(['installed', 'missing']);\nfunction auditHookOutput(entries) {\n  for (const e of entries ?? []) {\n    if (typeof e?.name !== 'string' || !VALID_STATES.has(e?.state) && typeof e?.state !== 'object') {\n      throw new Error(`Hook returned invalid state ${JSON.stringify(e?.state)} for ${e?.name}`);\n    }\n  }\n}","typeGuard":"const hasValidState = (e) => e != null && typeof e.name === 'string' && (e.state === 'installed' || e.state === 'missing' || (typeof e.state === 'object' && e.state !== null));","tryCatchPattern":"try { await misePkgInstall(plugin, pkgs) } catch (e) { const m = String(e).match(/hook returned invalid state '(.*?)' for '(.*?)'/); if (m) { console.error(`Fix plugin '${m[1]}': state '${m[2]}' — see hook contract docs`); } else throw e; }","preventionTips":["Write plugin hooks against the documented state vocabulary only ('installed', 'missing', structured variants).","Add a unit test for your Lua hook that asserts every returned entry's state is in the valid set.","Keep plugin and mise versions in sync — state strings are part of the hook contract.","Never hand-edit hook return values without re-checking the contract after mise upgrades."],"tags":["package-plugin","hook-contract","invalid-state","lua"],"backgroundTag":"unexpected-response-shape","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}