diff --git a/README.md b/README.md index d93ec0610c..bb78f03d3a 100644 --- a/README.md +++ b/README.md @@ -1492,6 +1492,8 @@ Python's standard-library HTTP client. Its fallback preserves spaces, Unicode, a reserved characters in search queries and label filters. File reads accept raw repository-relative paths and preserve spaces, Unicode, `#`, `?`, and literal percent signs with either HTTP client. +Directory listings ask for the repository's own default branch, so a repository that +uses neither `main` nor `master` reports its current tree instead of a stale branch. Users can explicitly activate an enabled skill for a single turn by starting the request with `/skill-name`, for example `/data-analysis analyze uploads/foo.csv`. DeerFlow loads that skill's `SKILL.md` as hidden current-turn context while leaving the base prompt limited to skill metadata. Slash activation respects disabled skills, custom-agent skill whitelists, and existing channel commands such as `/new` and `/help`. diff --git a/skills/public/github-deep-research/scripts/github_api.py b/skills/public/github-deep-research/scripts/github_api.py index 2e9b4dfdf9..dad88ec94a 100644 --- a/skills/public/github-deep-research/scripts/github_api.py +++ b/skills/public/github-deep-research/scripts/github_api.py @@ -101,17 +101,17 @@ class GitHubAPI: return f"[README not found: {e}]" def get_tree( - self, owner: str, repo: str, branch: str = "main", recursive: bool = True + self, owner: str, repo: str, branch: str = "HEAD", recursive: bool = True ) -> Dict: - """Get repository directory tree.""" + """Get repository directory tree. + + ``HEAD`` is resolved by GitHub to the repository's default branch, so a + repository that uses neither ``main`` nor ``master`` gets its current tree. + Guessing those two names instead served the tree of an abandoned branch + whenever the repository kept a stale ``master`` around. + """ params = {"recursive": "1"} if recursive else {} - try: - return self._get(f"/repos/{owner}/{repo}/git/trees/{branch}", params) - except Exception: - # Try 'master' if 'main' fails - if branch == "main": - return self._get(f"/repos/{owner}/{repo}/git/trees/master", params) - raise + return self._get(f"/repos/{owner}/{repo}/git/trees/{branch}", params) def get_file_content(self, owner: str, repo: str, path: str) -> str: """Get content of a file using its raw repository-relative path.""" diff --git a/tests/skills/test_github_deep_research.py b/tests/skills/test_github_deep_research.py index 2168e12443..d89683227c 100644 --- a/tests/skills/test_github_deep_research.py +++ b/tests/skills/test_github_deep_research.py @@ -130,3 +130,43 @@ def test_fallback_without_parameters(fallback_client): assert client.get_repo_info("owner", "repo") == {"ok": True} assert captured["url"] == "https://api.github.com/repos/owner/repo" + + +def test_tree_asks_for_the_repository_head(file_content_client): + client, captured = file_content_client + + assert client.get_tree("owner", "repo") == {"ok": True} + + url = urlsplit(captured["url"]) + assert url.path == "/repos/owner/repo/git/trees/HEAD" + assert parse_qs(url.query) == {"recursive": ["1"]} + + +def test_tree_keeps_an_explicit_branch(file_content_client): + client, captured = file_content_client + + assert client.get_tree("owner", "repo", branch="dev", recursive=False) == { + "ok": True + } + + url = urlsplit(captured["url"]) + assert url.path == "/repos/owner/repo/git/trees/dev" + assert url.query == "" + + +def test_tree_does_not_guess_another_branch(monkeypatch, fallback_client): + client, _ = fallback_client + asked: list[str] = [] + + def fail_after_recording(endpoint, params=None, accept=None): + asked.append(endpoint) + raise RuntimeError("HTTP 404") + + monkeypatch.setattr(client, "_get", fail_after_recording) + + with pytest.raises(RuntimeError): + client.get_tree("owner", "repo") + + # A stale `master` still resolves as a branch, so retrying names here silently + # returns the stale branch's tree instead of failing. + assert asked == ["/repos/owner/repo/git/trees/HEAD"]