{"record":{"id":"cb3bbcfb086a20f9","repo":"can1357/oh-my-pi","slug":"origin-must-have-exactly-one-kind-url","errorCode":null,"errorMessage":"origin must have exactly one {kind} url","messagePattern":"origin must have exactly one (.+?) url","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"python/robomp/src/proxy/server.py","lineNumber":310,"sourceCode":"        raise HTTPException(504, \"timeout reading origin url\") from exc\n    if proc.returncode != 0:\n        # `git remote get-url` writes nothing useful to stdout on failure; do\n        # NOT echo stderr to the client (may leak local paths). The proxy log\n        # already captured the failure.\n        log.warning(\"gh-proxy: failed to read origin url\", extra={\"repo_dir\": str(repo_dir)})\n        raise HTTPException(400, \"could not read origin url for worktree\")\n    return [line.strip() for line in proc.stdout.splitlines() if line.strip()]\n\n\ndef _read_single_remote_url(repo_dir: Path, expected_repo: str, *, push: bool, slot_uid: int | None = None) -> str:\n    urls = list(dict.fromkeys(_read_remote_urls(repo_dir, slot_uid=slot_uid, push=push)))\n    if len(urls) != 1:\n        kind = \"push\" if push else \"fetch\"\n        log.warning(\n            \"gh-proxy: refusing git op — origin has ambiguous remote urls\",\n            extra={\"expected_repo\": expected_repo, \"kind\": kind, \"count\": len(urls)},\n        )\n        raise HTTPException(400, f\"origin must have exactly one {kind} url\")\n    return urls[0]\n\n\ndef _normalized_github_https_url(url: str, expected_repo: str) -> str:\n    _validate_repo_name(expected_repo)\n    parsed = urlparse(url)\n    if (parsed.scheme or \"\").lower() != \"https\":\n        raise HTTPException(400, f\"remote url must be https://github.com/{expected_repo}[.git]\")\n    if parsed.username or parsed.password:\n        raise HTTPException(400, \"remote url must not contain embedded credentials\")\n    try:\n        port = parsed.port\n    except ValueError as exc:\n        raise HTTPException(400, \"remote url has invalid port\") from exc\n    if port is not None:\n        raise HTTPException(400, \"remote url must not specify a port\")\n    if (parsed.hostname or \"\").lower() != \"github.com\":\n        raise HTTPException(400, f\"remote url host must be github.com for repo {expected_repo!r}\")","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/robomp/src/proxy/server.py#L292-L328","documentation":"Raised when `git remote get-url origin` returns zero or multiple URLs for origin instead of exactly one. git can return several lines when a remote has multiple URLs configured (url multi-valued via `remote.origin.url` or multiple pushurl entries). The proxy requires an unambiguous single fetch/push URL to authenticate against GitHub.","triggerScenarios":"A git operation via the proxy where _read_single_remote_url finds the deduplicated URL list length != 1 for the requested kind (fetch or push): origin configured with multiple pushURLs, or a broken empty remote with no url.","commonSituations":"`git remote set-url --add --push origin <url>` used for split push/pull setups (e.g. push to a mirror); multi-remote relay configs copied into origin; a remote whose url entry was removed leaving zero urls.","solutions":["Inspect `git config --get-all remote.origin.url` and `git config --get-all remote.origin.pushurl` in the worktree","Collapse to a single URL: `git remote set-url origin <url>` and remove extra pushurls with `git config --unset-all remote.origin.pushurl` (or `git remote set-url --delete --push origin <extra>`)","If you need push/fan-out remotes, configure them on a different remote name, not origin","Re-add the remote cleanly: `git remote remove origin && git remote add origin <url>`"],"exampleFix":"// before: multiple push urls on origin\n$ git remote set-url --add --push origin https://github.com/acme/mirror.git\n// after: single url, move extra pushes elsewhere\n$ git remote set-url --delete --push origin https://github.com/acme/mirror.git\n$ git remote add mirror https://github.com/acme/mirror.git","handlingStrategy":"validation","validationCode":"import subprocess\n\ndef origin_has_single_url(repo_dir: str) -> bool:\n    urls = subprocess.run([\"git\", \"-C\", repo_dir, \"config\", \"--get-all\", \"remote.origin.url\"],\n                          capture_output=True, text=True, check=False).stdout.split()\n    return len(urls) == 1","typeGuard":null,"tryCatchPattern":"from fastapi import HTTPException\n\ntry:\n    result = client.post(\"/git/op\", json={\"repo\": \"acme/repo\"})\nexcept HTTPException as e:\n    if e.status_code == 400 and \"exactly one\" in e.detail:\n        normalize_origin_remote(worktree_dir)  # collapse multi-url origin\n    else:\n        raise","preventionTips":["Never use `git remote set-url --add --push` on origin in proxy-managed worktrees; use a separate remote","Audit worktrees with `git config --get-all remote.origin.url` during setup","Keep fan-out/mirror push configs on dedicated remotes"],"tags":["git","remote","configuration","proxy"],"backgroundTag":"ambiguous-git-remote-url","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}