{"record":{"id":"8ec41d3773ff233d","repo":"usestrix/strix","slug":"approved-must-be-a-boolean","errorCode":null,"errorMessage":"approved must be a boolean","messagePattern":"approved must be a boolean","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"strix/interface/tui/backend/controller.py","lineNumber":350,"sourceCode":"        if self._on_start is None:\n            raise RuntimeError(\"Scan start is unavailable\")\n        self._start_in_progress = True\n        try:\n            await self._on_start(verify)\n        finally:\n            self._start_in_progress = False\n        self.setup_mode = False\n        self.scan_started = True\n        self.scan_state = \"running\"\n\n    async def _confirm_mount(self, payload: dict[str, Any]) -> dict[str, Any]:\n        \"\"\"Answer the pending working-directory mount asked for in the live view.\"\"\"\n        mount = self.pending_workspace_mount\n        if mount is None:\n            raise RuntimeError(\"No mount confirmation is pending\")\n        approved = payload.get(\"approved\")\n        if not isinstance(approved, bool):\n            raise TypeError(\"approved must be a boolean\")\n        self.pending_workspace_mount = None\n        # Declining skips the mount, it does not abandon the scan. The prompt is\n        # the whole of the input either way; the working directory is only an\n        # extra the agent may look at, so the run goes ahead without one.\n        self.workspace_mount = mount if approved else None\n        await self._begin_scan(self._pending_verify)\n        return {\"approved\": approved}\n\n    async def _send_message(self, payload: dict[str, Any]) -> dict[str, Any]:\n        agent_id = self._required_string(payload, \"agent_id\")\n        message = self._required_string(payload, \"message\")\n        if self.coordinator is None:\n            raise RuntimeError(\"Agent coordinator is unavailable\")\n        if self.scan_loop is None or self.scan_loop.is_closed():\n            raise RuntimeError(\"Scan loop is not ready\")\n        self.live_view.record_user_message(agent_id, message)\n        if self.scan_loop is asyncio.get_running_loop():\n            delivered = await self.coordinator.send(","sourceCodeStart":332,"sourceCodeEnd":368,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/interface/tui/backend/controller.py#L332-L368","documentation":"Raised by the TUI controller's _confirm_mount handler when the payload sent to confirm (or decline) a pending working-directory mount does not carry a strict boolean 'approved' field. Strix requires an explicit true/false decision because the mount confirmation is a security-relevant consent gate; any ambiguous value (string \"true\", 1, null, missing key) is rejected with a TypeError. This guards against frontend/IPC serialization bugs that would silently coerce the user's choice.","triggerScenarios":"Calling the controller's confirm-mount command with payload {\"approved\": \"true\"}, {\"approved\": 1}, {\"approved\": null}, or omitting the key entirely while pending_workspace_mount is set. Typically happens when a custom TUI frontend or test harness JSON-encodes the flag as a non-bool type.","commonSituations":"Custom Bubble Tea/IPC clients serializing booleans as strings; test fixtures using 0/1 integers; payload builders that default missing values to None; protocol changes after upgrading Strix where the confirm message shape changed.","solutions":["Send {\"approved\": true} or {\"approved\": false} as a real JSON boolean, not a string or integer","Check the message schema in strix/interface/tui/backend/controller.py _confirm_mount and match the expected payload exactly","In test harnesses, use json.loads('{\"approved\": true}') rather than hand-built dicts with coerced values","If wrapping the controller, validate with isinstance(payload.get('approved'), bool) before dispatch"],"exampleFix":"// before\nawait controller.dispatch(\"confirm_mount\", {\"approved\": \"true\"})\n\n// after\nawait controller.dispatch(\"confirm_mount\", {\"approved\": True})","handlingStrategy":"type-guard","validationCode":"approved = payload.get(\"approved\")\nif not isinstance(approved, bool):\n    raise ValueError(\"payload['approved'] must be a JSON boolean\")","typeGuard":"def is_confirm_payload(p: dict) -> bool:\n    return isinstance(p.get(\"approved\"), bool)","tryCatchPattern":"try:\n    await controller.dispatch(\"confirm_mount\", payload)\nexcept TypeError as e:\n    if \"approved must be a boolean\" in str(e):\n        fix_payload_types()  # coerce/repair then resend once","preventionTips":["Always send JSON booleans for approval flags; never strings or 0/1","Centralize payload construction in one typed builder shared by UI and tests","In test suites, assert isinstance(approved, bool) before dispatch"],"tags":["tui","validation","payload","type-error"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}