GitHub Integration

Deep GitHub grounding — 7 agent tools for reading repos, plus OAuth connection, issue creation, and bidirectional status sync.

Overview

The GitHub integration does two jobs for Praxiom AI. First, it grounds the agent in your actual codebase — seven read-only tools let the agent inspect files, directory trees, commits, and open PRs before making a recommendation, so suggestions match what's really in main rather than hallucinated shape. Second, it pushes recommendations out as GitHub issues, with a back-channel sync that flips recommendation status to implemented when the issue closes.

Connecting

1

Initiate OAuth

Call POST /api/integrations/github/connect with workspace_id. The response returns { auth_url, state }. Redirect the user to auth_url. The server persists an OAuthState row with a 10-minute expiry.

{
  "workspace_id": "your-workspace-uuid"
}
2

Handle callback

GitHub redirects to GET /api/integrations/github/callback?code=...&state=.... The backend validates state against the DB row (one-time use, deletes on read), exchanges the code for an access token, fetches the authenticated user, encrypts the token with the workspace-level key, and upserts an Integration record with integration_type="github".

Config stored: github_user_id, github_username, github_avatar_url, selected_repos (initialized to []).

3

Select repositories

List accessible repos:

GET /api/integrations/github/repos?workspace_id={id}&search=...&page=1

Save the user's picks:

POST /api/integrations/github/repos/select
{
  "workspace_id": "your-workspace-uuid",
  "repos": ["org/repo-1", "org/repo-2"]
}

Only selected repos are visible to the agent's read tools and are the only valid targets for create_github_issue.

GitHub connection is gated on the has_github feature flag. Tokens are encrypted at rest; if GitHub returns 401, the integration surfaces a GITHUB_TOKEN_ERROR and the user must reconnect.

Agent Tools (Read)

These seven tools are what make GitHub more than a push target — the agent uses them to ground recommendations in the real code. All seven are read-only and do not go through HITL approval.

ToolArgsReturnsPurpose
read_repo_fileworkspace_id, path, repo?, start_line?, end_line?content, start_line, end_line, total_lines, total_bytes, truncated, shaRead a file. Pass start_line/end_line (1-indexed, inclusive) to slice a range. Without a range, a default cap trims by lines then bytes (never mid-character — UTF-8 safe).
list_repo_treeworkspace_id, repo?, path?, depth?entries[], total_entries, truncated, depthList files/dirs under path. depth is clamped to 1–5 (default 2). Echoes the clamped value so an agent asking for 99 sees it got 5.
search_repo_codeworkspace_id, query, repo?, path_filter?, max_results?matches[] (path + snippet), total_matches, truncatedGitHub code search. Supports qualifiers like language:python. max_results clamped 1–50. Note: GitHub's search API is rate-limited to 30 req/min — use sparingly.
list_recent_commitsworkspace_id, repo?, limit?, path?, since?commits[], total, limitRecent commits. limit clamped 1–100 (default 20). path filters to commits touching that path; since accepts ISO 8601.
list_open_pull_requestsworkspace_id, repo?, state?, limit?pull_requests[], total, state, limitPRs in the repo. state enum: open (default), closed, all; invalid values return error_code: invalid_state. limit clamped 1–50.
get_readmeworkspace_id, repo?content, path, total_lines, total_bytes, shaFetch the README. Agents call this on first contact to orient.
get_package_manifestworkspace_id, repo?detected, manifest_type, path, content, total_bytes, shaProbes for package.json, pyproject.toml, requirements.txt, go.mod, Cargo.toml, Gemfile, composer.json in that order. Returns the first found, or detected: false with paths_probed.

When the workspace has exactly one selected repo, repo can be omitted from any read tool — it's auto-resolved. With multiple selected repos, the tool returns a structured error asking the agent to pass repo explicitly.

Error shape

All seven tools translate GitHubServiceError into a structured payload the agent can reason over:

{
  "success": false,
  "error": "Repository 'org/missing' not found or access denied.",
  "error_code": "not_found"
}

Common error_code values: not_found, forbidden, rate_limited (with retry_after), token_expired, too_large, unknown.

Agent Tools (Write)

ToolArgsHITLPurpose
create_github_issueworkspace_id, title, body, repo?, labels?, recommendation_id?Yes (@gated)Create an issue. If recommendation_id is provided, auto-populates title/body via build_issue_body() and creates a RecommendationGitHubLink for sync.
list_github_issuesworkspace_id, repo?, state?, limit?NoList cached recommendation-linked issues. Filters: state enum all/open/closed; limit clamped 1–200.
get_github_issueworkspace_id, repo, issue_numberNoFetch one linked issue. If an OAuth token is present, syncs fresh state from GitHub and updates the cache before returning.

create_github_issue is gated — calling it from the agent returns { status: "queued", pending_action_id }. The user approves the action from the pending-actions inbox; the executor then calls the __original__ handler with gating disabled.

Creating Issues (API)

Push a recommendation to GitHub as an issue:

POST /api/integrations/github/issues
{
  "workspace_id": "your-workspace-uuid",
  "recommendation_id": "rec-uuid",
  "repo": "org/repo-name",
  "title": "Improve onboarding flow",
  "body": "Based on user research...",
  "labels": ["enhancement", "priority:high"]
}

The endpoint validates the repo is in selected_repos (returns REPO_NOT_SELECTED otherwise), calls GitHubService.create_issue, and persists a RecommendationGitHubLink row with the issue number, URL, title, and state. The response includes the issue_url, issue_number, and the link_id used by sync.

Status Sync

POST /api/integrations/github/sync
{
  "workspace_id": "your-workspace-uuid"
}

For each link in the workspace, the endpoint calls GET /repos/{repo}/issues/{n}, updates github_issue_state and last_synced_at on the link, and — if the issue has a linked PR — stores github_pr_url. When an issue transitions to closed, the linked Recommendation.status is flipped to implemented (unless it was already implemented or rejected).

Response:

{
  "synced_count": 42,
  "updated_count": 3
}

Sync is manual — there are no webhooks in the current cut. Call /sync on a cron or user action. Individual agent tools (get_github_issue) also opportunistically sync when the user inspects a single issue.

Management Endpoints

EndpointPurpose
GET /api/integrations/github/status?workspace_id={id}connected, github_username, github_avatar_url, repos_count, selected_repos, last_synced_at
GET /api/integrations/github/repos?workspace_id={id}&search=...&page=1List accessible repos (paginated, client-side search filter)
POST /api/integrations/github/repos/selectSave selected_repos
POST /api/integrations/github/issuesCreate issue + link
GET /api/integrations/github/links?workspace_id={id}&recommendation_id=...List recommendation-issue links
POST /api/integrations/github/syncBulk sync all links
DELETE /api/integrations/github/disconnectRemoves all links + the Integration row

Key Concepts

  • Selected-repo firewall — the agent can only read and write to repos the user has explicitly picked. An unknown repo in any tool returns a typed error listing the available ones.
  • Bidirectional traceability — every linked issue knows its source recommendation; every recommendation with a link knows its issue state. Close the loop from user evidence → recommendation → issue → merged PR.
  • Truncation is visible — read tools always return truncated: bool and line/byte counts so the agent knows when it's only seen part of a file and can issue a follow-up slice.
  • Gated writes — only create_github_issue is a write; it goes through the pending-actions queue so the user approves the title/body/labels/repo before anything hits GitHub.

What's Next

Was this helpful?