-
Notifications
You must be signed in to change notification settings - Fork 5
feat: auto-detect GitHub auth via gh CLI #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Add automatic GitHub authentication that works out of the box for users with GitHub CLI installed. No manual token configuration needed. Authentication fallback chain: 1. GH_TOKEN environment variable 2. GITHUB_TOKEN environment variable 3. gh auth token (GitHub CLI) Also improves error messages to be context-aware: - With token: suggests checking scopes/permissions - Without token + gh installed: suggests `gh auth login` - Without token + no gh: shows both options 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds automatic GitHub authentication detection that works seamlessly with GitHub CLI, eliminating the need for manual token configuration. The implementation uses a well-designed fallback chain (GH_TOKEN → GITHUB_TOKEN → gh CLI) and provides context-aware error messages that guide users to the appropriate authentication solution based on their setup.
Key changes:
- New authentication module with pluggable token resolvers and automatic fallback
- Context-aware error messages that adapt based on token availability and gh CLI presence
- Updated GitHub integration to use the new auth module throughout
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/skillport/shared/auth.py |
New authentication module implementing token resolution with fallback chain and gh CLI integration |
src/skillport/shared/__init__.py |
Exports new auth module functions and types |
src/skillport/modules/skills/internal/github.py |
Integrates new auth module; adds context-aware error messages for 404/403 responses |
src/skillport/modules/skills/public/update.py |
Updates to use new auth module instead of direct environment variable access |
src/skillport/modules/skills/internal/validation.py |
Minor formatting improvement (blank line addition) |
src/skillport/interfaces/cli/commands/validate.py |
Code style improvements (list comprehension formatting) |
guide/configuration.md |
Documents new authentication fallback chain with examples |
guide/cli.md |
Adds comprehensive GitHub authentication section with fallback chain documentation |
README.md |
Adds example showing private repo access with gh CLI |
uv.lock |
Version bump to 0.5.2 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| """GitHub authentication token resolution with fallback chain. | ||
|
|
||
| Design: Function-based with pluggable resolvers for easy extension. | ||
|
|
||
| Fallback chain (in order): | ||
| 1. GH_TOKEN environment variable (fine-grained PAT recommended by GitHub) | ||
| 2. GITHUB_TOKEN environment variable (classic, widely used) | ||
| 3. gh CLI auth token (for local development) | ||
|
|
||
| Usage: | ||
| from skillport.shared.auth import resolve_github_token | ||
|
|
||
| result = resolve_github_token() | ||
| if result.token: | ||
| headers["Authorization"] = f"Bearer {result.token}" | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import os | ||
| import subprocess | ||
| from collections.abc import Callable | ||
| from dataclasses import dataclass | ||
|
|
||
| # Type alias for resolver functions | ||
| TokenResolver = Callable[[], "TokenResult | None"] | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class TokenResult: | ||
| """Result of token resolution with source information.""" | ||
|
|
||
| token: str | None | ||
| source: str | None # e.g., "GH_TOKEN", "GITHUB_TOKEN", "gh_cli" | ||
|
|
||
| @property | ||
| def has_token(self) -> bool: | ||
| return bool(self.token) | ||
|
|
||
| def __bool__(self) -> bool: | ||
| return self.has_token | ||
|
|
||
|
|
||
| # --- Token Resolvers (each returns TokenResult or None) --- | ||
|
|
||
|
|
||
| def _resolve_from_gh_token_env() -> TokenResult | None: | ||
| """Resolve from GH_TOKEN (preferred for fine-grained PAT).""" | ||
| if token := os.getenv("GH_TOKEN"): | ||
| return TokenResult(token=token, source="GH_TOKEN") | ||
| return None | ||
|
|
||
|
|
||
| def _resolve_from_github_token_env() -> TokenResult | None: | ||
| """Resolve from GITHUB_TOKEN (classic, widely used).""" | ||
| if token := os.getenv("GITHUB_TOKEN"): | ||
| return TokenResult(token=token, source="GITHUB_TOKEN") | ||
| return None | ||
|
|
||
|
|
||
| def _resolve_from_gh_cli() -> TokenResult | None: | ||
| """Resolve from gh CLI auth token.""" | ||
| try: | ||
| result = subprocess.run( | ||
| ["gh", "auth", "token"], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=5, | ||
| ) | ||
| if result.returncode == 0 and (token := result.stdout.strip()): | ||
| return TokenResult(token=token, source="gh_cli") | ||
| except FileNotFoundError: | ||
| # gh CLI not installed | ||
| pass | ||
| except subprocess.TimeoutExpired: | ||
| # gh CLI timed out | ||
| pass | ||
| except Exception: | ||
| # Any other error (permissions, etc.) | ||
| pass | ||
| return None | ||
|
|
||
|
|
||
| # --- Fallback Chain Configuration --- | ||
|
|
||
| # Order matters: first match wins | ||
| # To customize, modify this list or use resolve_github_token(resolvers=[...]) | ||
| DEFAULT_RESOLVERS: list[TokenResolver] = [ | ||
| _resolve_from_gh_token_env, | ||
| _resolve_from_github_token_env, | ||
| _resolve_from_gh_cli, | ||
| ] | ||
|
|
||
|
|
||
| def resolve_github_token( | ||
| resolvers: list[TokenResolver] | None = None, | ||
| ) -> TokenResult: | ||
| """Resolve GitHub token using fallback chain. | ||
|
|
||
| Args: | ||
| resolvers: Custom list of resolver functions. Defaults to DEFAULT_RESOLVERS. | ||
|
|
||
| Returns: | ||
| TokenResult with token and source, or empty TokenResult if none found. | ||
| """ | ||
| for resolver in resolvers or DEFAULT_RESOLVERS: | ||
| if result := resolver(): | ||
| return result | ||
| return TokenResult(token=None, source=None) | ||
|
|
||
|
|
||
| def is_gh_cli_available() -> bool: | ||
| """Check if gh CLI is installed and available.""" | ||
| try: | ||
| result = subprocess.run( | ||
| ["gh", "--version"], | ||
| capture_output=True, | ||
| timeout=5, | ||
| ) | ||
| return result.returncode == 0 | ||
| except (FileNotFoundError, subprocess.TimeoutExpired, Exception): | ||
| return False |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new authentication module lacks test coverage. Given that the repository has comprehensive tests for other GitHub-related functionality (e.g., test_github_source.py), the authentication resolution logic should also have tests covering:
- Token resolution from each source (GH_TOKEN, GITHUB_TOKEN, gh CLI)
- Fallback chain behavior
- Error handling (gh CLI not installed, timeout, etc.)
- TokenResult properties and boolean conversion
- is_gh_cli_available function
Consider adding a test file like tests/unit/test_auth.py to ensure the authentication chain works correctly and handles edge cases.
Summary
Authentication Fallback Chain
GH_TOKENenvironment variableGITHUB_TOKENenvironment variablegh auth token(GitHub CLI)Error Message Improvements
gh auth loginChanges
src/skillport/shared/auth.py- Token resolution with pluggable resolversgithub.py- Use new auth module, context-aware error messagesupdate.py- Use new auth moduleTest plan
uv run pytest tests/ -x -q- All tests passuv run verify_server.py- Server verification passesgh auth login🤖 Generated with Claude Code