{"record":{"id":"df05eaa82ca013f1","repo":"zed-industries/zed","slug":"agent-tool-permissions-should-be-an-object-or-null","errorCode":null,"errorMessage":"agent.tool_permissions should be an object or null when migrating always_allow_tool_actions","messagePattern":"agent\\.tool_permissions should be an object or null when migrating always_allow_tool_actions","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/migrator/src/migrations/m_2026_02_04/settings.rs","lineNumber":77,"sourceCode":"            false\n        }\n    };\n\n    if should_migrate_always_allow {\n        if matches!(\n            agent_object.get(TOOL_PERMISSIONS_KEY),\n            None | Some(Value::Null)\n        ) {\n            agent_object.insert(\n                TOOL_PERMISSIONS_KEY.to_string(),\n                Value::Object(Default::default()),\n            );\n        }\n\n        let Some(Value::Object(tool_permissions_object)) =\n            agent_object.get_mut(TOOL_PERMISSIONS_KEY)\n        else {\n            bail!(\n                \"agent.tool_permissions should be an object or null when migrating \\\n                 always_allow_tool_actions\"\n            );\n        };\n\n        if !tool_permissions_object.contains_key(DEFAULT_KEY)\n            && !tool_permissions_object.contains_key(DEFAULT_MODE_KEY)\n        {\n            tool_permissions_object\n                .insert(DEFAULT_KEY.to_string(), Value::String(\"allow\".to_string()));\n        }\n    }\n\n    if let Some(tool_permissions) = agent_object.get_mut(TOOL_PERMISSIONS_KEY) {\n        migrate_default_mode_to_default(tool_permissions)?;\n    }\n\n    Ok(())","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/zed-industries/zed/blob/f4178619acd0d47ea1f76a2025c42962c6d6638c/crates/migrator/src/migrations/m_2026_02_04/settings.rs#L59-L95","documentation":"The m_2026_02_04 migration moves the legacy `agent.always_allow_tool_actions` list into `agent.tool_permissions.default`. It tolerates `tool_permissions` being absent or null (inserting a fresh object), but if the existing value is a string, number, array, or boolean it cannot be treated as a map and the migration bails.","triggerScenarios":"settings.json contains \"agent\": { \"tool_permissions\": \"allow\" } (or an array like [\"edit\"]) instead of an object or null, and the always_allow_tool_actions migration runs on startup.","commonSituations":"Users abbreviating the permission model as a single string; copied snippets from pre-release agent docs that used a flat list; hand-merging old and new agent config shapes.","solutions":["Change tool_permissions to an object, e.g. \"agent\": { \"tool_permissions\": { \"default\": \"allow\" } }","Or set it to null (or delete the key) so the migration inserts the correct default structure","If you also have always_allow_tool_actions, keep it as a string array and let the migration fold it into tool_permissions.default"],"exampleFix":"// before\n\"agent\": { \"tool_permissions\": \"allow\" }\n\n// after\n\"agent\": { \"tool_permissions\": { \"default\": \"allow\" } }","handlingStrategy":"validation","validationCode":"fn tool_permissions_ok(value: &serde_json::Value) -> bool {\n    value\n        .pointer(\"/agent/tool_permissions\")\n        .map_or(true, |tp| tp.is_object() || tp.is_null())\n}","typeGuard":"fn is_tool_permissions_migratable(v: &serde_json::Value) -> bool {\n    v.pointer(\"/agent/tool_permissions\")\n        .map_or(true, |tp| tp.is_object() || tp.is_null())\n}","tryCatchPattern":"if let Err(e) = run_migration(&mut settings) {\n    if e.to_string().contains(\"tool_permissions\") {\n        // replace the non-object value with {} or null, then retry\n    }\n}","preventionTips":["Never write tool_permissions as a bare string or array","Keep permission mode inside an object keyed by tool name plus `default`","After agent-config schema changes, re-check the nested shape"],"tags":["zed","settings","migration","agent","permissions"],"backgroundTag":"settings-schema-validation-failed","analyzedSha":"f4178619acd0d47ea1f76a2025c42962c6d6638c","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}