{"record":{"id":"c408cad8be47826f","repo":"can1357/oh-my-pi","slug":"ensure-workspace-accepts-either-pr-head-or-existin","errorCode":null,"errorMessage":"ensure_workspace accepts either pr_head or existing_branch, not both","messagePattern":"ensure_workspace accepts either pr_head or existing_branch, not both","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/robomp/src/sandbox.py","lineNumber":895,"sourceCode":"\n    def ensure_workspace(\n        self,\n        *,\n        repo: str,\n        number: int,\n        title: str,\n        clone_url: str,\n        default_branch: str,\n        existing_branch: str | None = None,\n        pr_head: int | None = None,\n        author_name: str,\n        author_email: str,\n        slot_uid: int | None = None,\n    ) -> Workspace:\n        \"\"\"Create or resume a per-issue worktree.\"\"\"\n        with self._repo_lock(repo):\n            if pr_head is not None and existing_branch is not None:\n                raise ValueError(\"ensure_workspace accepts either pr_head or existing_branch, not both\")\n            pool = self.ensure_clone(repo=repo, clone_url=clone_url, default_branch=default_branch)\n            ws_root = self.workspace_root(repo, number)\n            repo_dir = ws_root / \"repo\"\n            session_dir = ws_root / \".omp-session\"\n            context_dir = ws_root / \"context\"\n            artifacts_dir = ws_root / \"artifacts\"\n            for path in (ws_root, session_dir, context_dir, context_dir / \"repro\", artifacts_dir):\n                path.mkdir(parents=True, exist_ok=True)\n\n            branch = (\n                f\"review/pr-{pr_head}\"\n                if pr_head is not None\n                else existing_branch\n                or make_branch(\n                    issue_number=number,\n                    title=title,\n                    seed=f\"{repo}#{number}\",\n                )","sourceCodeStart":877,"sourceCodeEnd":913,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/robomp/src/sandbox.py#L877-L913","documentation":"ensure_workspace() creates one workspace per issue and accepts exactly one branch source: pr_head (detached checkout of a fetched PR head) or existing_branch (resume an existing branch). Passing both is a caller contract violation — the two modes are mutually exclusive — so it raises ValueError up front before touching the clone pool.","triggerScenarios":"Calling SandboxManager.ensure_workspace(repo, number, ..., pr_head=X, existing_branch=Y) with both keyword arguments set to non-None values, e.g. a dispatcher bug that always passes the PR head while a resume path also supplies the stored branch.","commonSituations":"Handler code that merges 'new PR task' and 'resume existing task' inputs without branching on which is present; refactoring tasks.py so an optional pr_head default stopped being None on the resume path; unit tests constructing kwargs dicts with both keys.","solutions":["Pass exactly one: use pr_head only for fresh PR-review workspaces, existing_branch only when resuming prior work.","Add explicit branching in the caller: `kwargs['pr_head' if pr else 'existing_branch'] = value`.","If both values genuinely exist, decide precedence (resume wins) and drop the other before calling.","Re-run/replay the event after fixing the dispatcher so the failed event is retried with valid kwargs."],"exampleFix":"// before\nmanager.ensure_workspace(repo=repo, number=n, clone_url=url, default_branch=main,\n    author_name=a, author_email=e, pr_head=pr_head, existing_branch=branch)\n// after\nkwargs = {}\nif pr_head is not None:\n    kwargs[\"pr_head\"] = pr_head\nelif branch is not None:\n    kwargs[\"existing_branch\"] = branch\nmanager.ensure_workspace(repo=repo, number=n, clone_url=url, default_branch=main,\n    author_name=a, author_email=e, **kwargs)","handlingStrategy":"validation","validationCode":"if pr_head is not None and existing_branch is not None:\n    raise ValueError(\"caller bug: pr_head and existing_branch are mutually exclusive\")\n\nws = manager.ensure_workspace(..., **({\"pr_head\": pr_head} if pr_head else {\"existing_branch\": existing_branch}))","typeGuard":null,"tryCatchPattern":"try:\n    ws = manager.ensure_workspace(...)\nexcept ValueError as e:\n    if \"not both\" in str(e):\n        log.error(\"dispatcher bug: both pr_head and existing_branch set\", exc_info=True)\n    raise","preventionTips":["Build ensure_workspace kwargs conditionally — exactly one branch source.","Add a unit test covering both the pr_head and existing_branch call shapes.","Keep new-task and resume-task code paths separate instead of merging optional kwargs."],"tags":["validation","api-misuse","worktree"],"backgroundTag":"invalid-arguments","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}