{"record":{"id":"2376668f84895473","repo":"jdx/mise","slug":"package-hook-did-not-return-state-for","errorCode":null,"errorMessage":"{} package hook did not return state for '{}'","messagePattern":"(.+?) package hook did not return state for '(.+?)'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/plugin.rs","lineNumber":569,"sourceCode":"                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    }\n\n    async fn install(&self, pkgs: &[PackageRequest], opts: &InstallOpts) -> Result<()> {\n        if opts.dry_run {\n            return self.action(pkgs, opts, false).await;\n        }\n        let _lock = self.operation_lock()?;","sourceCodeStart":551,"sourceCodeEnd":587,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/plugin.rs#L551-L587","documentation":"When mise asks a package plugin for a package's status, the hook is expected to return a state entry for every requested package. If the returned list contains no entry for a requested package (`None` case in the match), `installed` fails with the plugin name and package name rather than inventing a default status. This guarantees status checks are total: every request gets an explicit answer from the hook.","triggerScenarios":"Calling `installed` (via prune_plan, apply_prune_plan, install, or upgrade) where the plugin's status hook returns a list that omits the requested package entirely — e.g. the hook filters out packages it does not recognize or its query silently skips failures.","commonSituations":"A plugin hook that only reports packages present in its own config, while mise asks about a package added elsewhere; a Lua hook bug where an error for one package causes it to be dropped from the result; name normalization differences (case/alias) between mise's request and the hook's keys.","solutions":["Fix the plugin's status hook to return an entry (installed or missing) for every requested package name.","Check for name mismatches between the request and the hook's reported names (aliases, case, versioned names) and normalize them in the hook.","Run the plugin's status hook manually with the failing package name to see why it is omitted.","Update the plugin to a version that handles unknown/missing packages by reporting state 'missing' instead of omitting them."],"exampleFix":"-- before: hook omits untracked packages\nlocal results = {}\nfor _, p in ipairs(tracked) do table.insert(results, p) end\nreturn results\n\n-- after: always answer for every requested package\nfor _, req in ipairs(requested) do\n  if not find(results, req.name) then\n    table.insert(results, { name = req.name, state = 'missing' })\n  end\nend\nreturn results","handlingStrategy":"type-guard","validationCode":"// Ensure the hook answers for every requested package before calling mise\nfunction assertTotal(requested, returned) {\n  const got = new Set((returned ?? []).map((e) => e?.name));\n  const missing = requested.filter((r) => !got.has(r.name));\n  if (missing.length) throw new Error(`Hook omitted state for: ${missing.map((m) => m.name).join(', ')}`);\n}","typeGuard":"const answersAll = (requested, returned) => { const got = new Set((returned ?? []).map((e) => e?.name)); return requested.every((r) => got.has(r.name)); };","tryCatchPattern":"try { await misePkgInstall(plugin, pkgs) } catch (e) { const m = String(e).match(/hook did not return state for '(.*?)'/); if (m) { console.error(`Plugin hook omitted '${m[1]}' — normalize names or default to state 'missing'`); } else throw e; }","preventionTips":["Design hooks to return an entry for EVERY requested package, defaulting to state 'missing'.","Normalize package names (case, aliases) identically in requests and hook responses.","Test the hook with a package it does not track to confirm it still answers 'missing'.","Log hook internals during development so dropped entries surface early."],"tags":["package-plugin","hook-contract","missing-result","lua"],"backgroundTag":"empty-result-set","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"}