{"record":{"id":"4ce1830bc78ab08b","repo":"HKUDS/DeepTutor","slug":"exc-4ce183","errorCode":null,"errorMessage":"{exc}","messagePattern":"\\{exc\\}","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"deeptutor/api/routers/subagents.py","lineNumber":171,"sourceCode":"            raise HTTPException(\n                status_code=400, detail=\"A partner_id is required to connect a partner.\"\n            )\n        # Partners are admin-managed, but an admin can assign one to a user via\n        # the grant system. An admin may connect any partner; a non-admin only a\n        # partner assigned to them (403 otherwise). The partner still runs in its\n        # own isolated scope — connecting just lets the user consult it in chat.\n        assert_partner_allowed(partner_id)\n        from deeptutor.services.partners import get_partner_manager\n\n        if not get_partner_manager().partner_exists(partner_id):\n            raise HTTPException(status_code=400, detail=f\"No partner named {partner_id!r}.\")\n    else:\n        raw_cwd = (payload.cwd or \"\").strip()\n        if raw_cwd:\n            try:\n                resolved_cwd = str(assert_path_allowed(raw_cwd))\n            except ValueError as exc:\n                raise HTTPException(status_code=400, detail=str(exc)) from exc\n\n    try:\n        manager = current_kb_manager()\n        entry = manager.register_subagent_connection(\n            name, agent_kind, cwd=resolved_cwd, partner_id=partner_id\n        )\n    except ValueError as exc:\n        raise HTTPException(status_code=400, detail=str(exc)) from exc\n    except Exception as exc:  # pragma: no cover - defensive\n        logger.error(\"Error connecting subagent: %s\", exc)\n        raise HTTPException(status_code=500, detail=str(exc)) from exc\n\n    return {\n        \"status\": \"connected\",\n        \"name\": name,\n        \"agent_kind\": entry[\"agent_kind\"],\n        \"cwd\": entry[\"cwd\"],\n        \"partner_id\": entry.get(\"partner_id\", \"\"),","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/api/routers/subagents.py#L153-L189","documentation":"For non-partner connections, an optional cwd must pass assert_path_allowed(); a ValueError (path outside allowed roots, nonexistent, or not a directory) is surfaced as 400 with the original message.","triggerScenarios":"POST /connections with cwd like \"/etc\" or any path outside the configured allowed roots, or a path that cannot be resolved.","commonSituations":"Users entering arbitrary absolute paths; sandbox/allowlist tightened after the client was written; relative path resolved against an unexpected cwd.","solutions":["Use a path under an allowed root (e.g. the workspace/project dir)","Check the allowed-paths configuration for the server","Omit cwd entirely if you don't need to scope the agent to a directory"],"exampleFix":"// before\n{\"cwd\": \"/etc/ssl\"}\n// after\n{\"cwd\": \"/home/me/projects/my-repo\"}","handlingStrategy":"validation","validationCode":"const resp = await fetch('/api/v1/system/allowed-paths'); // or use known workspace root\nconst cwd = pathInsideWorkspace(userPath, workspaceRoot);\nif (!cwd) { showPathError(); return; }","typeGuard":"const isAllowedPath = (p: string) => p.startsWith(WORKSPACE_ROOT + '/');","tryCatchPattern":"try { await createConnection(p); } catch (e) { if (e.status === 400) showPathError(e.detail); }","preventionTips":["Offer a directory picker scoped to allowed roots","Omit cwd when scoping isn't needed"],"tags":["http-400","path-validation","subagent","api"],"backgroundTag":"path-not-allowed","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}