{"record":{"id":"b3c11711596ecaf5","repo":"openai/codex","slug":"missing-optional-dependency-platformpackage-re","errorCode":null,"errorMessage":"Missing optional dependency ${platformPackage}. Reinstall Codex: ${updateCommand}","messagePattern":"Missing optional dependency (.+?)\\. Reinstall Codex: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"codex-cli/bin/codex.js","lineNumber":105,"sourceCode":"\n  const codexExecutable = path.join(\n    vendorRoot,\n    targetTriple,\n    \"bin\",\n    process.platform === \"win32\" ? \"codex.exe\" : \"codex\",\n  );\n  if (existsSync(codexExecutable)) {\n    return codexExecutable;\n  }\n\n  const packageManager = detectPackageManager();\n  const updateCommand =\n    packageManager === \"bun\"\n      ? \"bun install -g @openai/codex@latest\"\n      : packageManager === \"pnpm\"\n        ? \"pnpm add -g @openai/codex@latest\"\n        : \"npm install -g @openai/codex@latest\";\n  throw new Error(\n    `Missing optional dependency ${platformPackage}. Reinstall Codex: ${updateCommand}`,\n  );\n}\n\nconst binaryPath = findCodexExecutable();\n\n// Use an asynchronous spawn instead of spawnSync so that Node is able to\n// respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is\n// executing. This allows us to forward those signals to the child process\n// and guarantees that when either the child terminates or the parent\n// receives a fatal signal, both processes exit in a predictable manner.\n\nfunction isPnpmOwnedCodexInstall(nodeModulesDir) {\n  if (!existsSync(path.join(nodeModulesDir, \".modules.yaml\"))) {\n    return false;\n  }\n\n  try {","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-cli/bin/codex.js#L87-L123","documentation":"normalize_query_name rejects an empty query-parameter name. It is called from validate_query_constraints at config-load time and again when a QueryConstraint is compiled for live request matching, so an empty key fails twice on the same config. The message matches the defensive check in validate_query_constraints, but this is the site that actually fires first for an empty key. The proxy needs the name to select which query parameter to match, so an empty name is always a config bug.","triggerScenarios":"An empty-string key in a hook's match.query map — TOML query = { \"\" = [...] } or a Rust-built map with an empty String key. Hit once via validate_mitm_hook_config at startup and again via QueryConstraint construction during hook compilation.","commonSituations":"Same shapes as the duplicate check at mitm_hook.rs:566: template-generated TOML with unfilled placeholders, programmatic map construction with computed (possibly empty) keys, and copy-pasted hook examples with the key name stripped.","solutions":["Delete the empty-string key from match.query","Restore the intended parameter name, e.g. query = { ref = [\"heads/main\"] }","Sanitize generated configs before load: reject or drop empty map keys in the generator"],"exampleFix":"// config.toml — before\n[network.mitm_hooks.match.query]\n\"\" = [\"heads/main\"]\n\n// after\n[network.mitm_hooks.match.query]\nref = [\"heads/main\"]","handlingStrategy":"validation","validationCode":"// Runs before both validation and hook compilation\nif hook.matcher.query.keys().any(|k| k.is_empty()) {\n    return Err(anyhow!(\"mitm hook for {} has an empty query key\", hook.host));\n}","typeGuard":"fn query_keys_valid(hook: &MitmHookConfig) -> bool {\n    hook.matcher.query.keys().all(|k| !k.is_empty())\n}","tryCatchPattern":null,"preventionTips":["Remember the empty-key check fires at both validate and compile time — fix the config, not one call site","Reject empty keys at the boundary where configs are generated or merged","Add a unit test asserting example configs in docs/repo pass validate_mitm_hook_config"],"tags":["rust","network-proxy","mitm-hooks","query-string","config-validation","toml"],"backgroundTag":"config-validation-failed","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}