Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2148,7 +2148,7 @@ export function createApp() {
const unauthorized = await requireContributorAccess(c, login);
if (unauthorized) return unauthorized;
const [github, pullRequests, issues, cachedRepoStats, gittensorSnapshot] = await Promise.all([
fetchPublicContributorProfile(login),
fetchPublicContributorProfile(login, c.env),
listContributorPullRequests(c.env, login),
listContributorIssues(c.env, login),
listContributorRepoStats(c.env, login),
Expand Down Expand Up @@ -3784,7 +3784,7 @@ async function loadOpenQueueCounts(env: Env, fullName: string): Promise<{ openIs

async function loadContributorFastContext(env: Env, login: string) {
const [github, contributorPullRequests, contributorIssues, repositories, syncStates, syncSegments, cachedRepoStats, gittensorSnapshot] = await Promise.all([
fetchPublicContributorProfile(login),
fetchPublicContributorProfile(login, env),
listContributorPullRequests(env, login),
listContributorIssues(env, login),
listRepositories(env),
Expand Down
5 changes: 4 additions & 1 deletion src/github/public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,15 @@ const repoStatsCache = new Map<string, RepoStatsCacheEntry>();
// indefinitely (mirrors GITHUB_FETCH_TIMEOUT_MS in src/github/app.ts) (#790).
const GITHUB_PUBLIC_FETCH_TIMEOUT_MS = 12_000;

export async function fetchPublicContributorProfile(login: string): Promise<PublicContributorProfile> {
export async function fetchPublicContributorProfile(login: string, env?: Pick<Env, "GITHUB_PUBLIC_TOKEN">): Promise<PublicContributorProfile> {
const safeLogin = encodeURIComponent(login);
const headers = {
accept: "application/vnd.github+json",
"user-agent": "gittensory/0.1",
"x-github-api-version": "2022-11-28",
// Authenticated requests lift the 60/hr unauthenticated ceiling to 5000/hr so the 500-login evidence
// loop doesn't exhaust it and silently degrade (mirrors fetchPublicRepoStats) (#790).
...(env?.GITHUB_PUBLIC_TOKEN ? { authorization: `Bearer ${env.GITHUB_PUBLIC_TOKEN}` } : {}),
};
try {
const signal = AbortSignal.timeout(GITHUB_PUBLIC_FETCH_TIMEOUT_MS);
Expand Down
4 changes: 2 additions & 2 deletions src/mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1485,7 +1485,7 @@ export class GittensoryMcp {
private async getContributorProfile(login: string): Promise<ToolPayload> {
this.requireContributorAccess(login);
const [github, pullRequests, issues, cachedRepoStats, gittensorSnapshot] = await Promise.all([
fetchPublicContributorProfile(login),
fetchPublicContributorProfile(login, this.env),
listContributorPullRequests(this.env, login),
listContributorIssues(this.env, login),
listContributorRepoStats(this.env, login),
Expand Down Expand Up @@ -2026,7 +2026,7 @@ export class GittensoryMcp {

private async loadContributorFastContext(login: string) {
const [github, contributorPullRequests, contributorIssues, repositories, syncStates, cachedRepoStats, gittensorSnapshot] = await Promise.all([
fetchPublicContributorProfile(login),
fetchPublicContributorProfile(login, this.env),
listContributorPullRequests(this.env, login),
listContributorIssues(this.env, login),
listRepositories(this.env),
Expand Down
4 changes: 2 additions & 2 deletions src/queue/processors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ async function buildContributorEvidence(env: Env, login?: string): Promise<void>
// 500-login batch and poison-pill the queue on retry (#787).
try {
const [github, contributorPullRequests, contributorIssues, cachedRepoStats, gittensorSnapshot] = await Promise.all([
fetchPublicContributorProfile(contributorLogin),
fetchPublicContributorProfile(contributorLogin, env),
listContributorPullRequests(env, contributorLogin),
listContributorIssues(env, contributorLogin),
listContributorRepoStats(env, contributorLogin),
Expand Down Expand Up @@ -1270,7 +1270,7 @@ async function maybePublishPrPublicSurface(
if (!prelimHasPublicOutput) return;
if (publicSurfaceSkipped || !official || !author) return;

const [github] = await Promise.all([fetchPublicContributorProfile(author)]);
const [github] = await Promise.all([fetchPublicContributorProfile(author, env)]);
const contributorPullRequests: Awaited<ReturnType<typeof listContributorPullRequests>> = [];
const contributorIssues: Awaited<ReturnType<typeof listContributorIssues>> = [];
const repoStats: Awaited<ReturnType<typeof listContributorRepoStats>> = official.status === "confirmed" ? contributorRepoStatsFromGittensor(official.snapshot) : [];
Expand Down
2 changes: 1 addition & 1 deletion src/services/agent-orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ async function executeLocalBranchRun(env: Env, run: AgentRunRecord, kind: string
async function analyzeLocalBranch(env: Env, input: LocalBranchAnalysisInput): Promise<LocalBranchAnalysis & { dataQuality?: { status: "complete" | "degraded" | "blocked" | "unknown"; warnings: string[] } }> {
const [github, contributorPullRequests, contributorIssues, repositories, syncStates, cachedRepoStats, gittensorSnapshot, repo, issues, pullRequests, recentMergedPullRequests, bounties, scoringSnapshot, issueQuality, repoManifest] =
await Promise.all([
fetchPublicContributorProfile(input.login),
fetchPublicContributorProfile(input.login, env),
listContributorPullRequests(env, input.login),
listContributorIssues(env, input.login),
listRepositories(env),
Expand Down
2 changes: 1 addition & 1 deletion src/services/decision-pack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ export async function buildAndPersistContributorDecisionPack(env: Env, login: st
// The heavy full-table reads are login-independent; reuse caller-provided context (batch job) or load once here (single-login run).
const { repositories, syncStates, syncSegments, totals, allIssues, allPullRequests, bounties, scoringSnapshot } = shared ?? (await loadDecisionPackSharedInputs(env));
const [github, contributorPullRequests, contributorIssues, cachedRepoStats, gittensorSnapshot] = await Promise.all([
fetchPublicContributorProfile(login),
fetchPublicContributorProfile(login, env),
listContributorPullRequests(env, login),
listContributorIssues(env, login),
listContributorRepoStats(env, login),
Expand Down
16 changes: 16 additions & 0 deletions test/unit/adapters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,20 @@ describe("small adapters and normalizers", () => {
vi.stubGlobal("fetch", async () => new Response("nope", { status: 500 }));
await expect(fetchPublicContributorProfile("missing")).resolves.toMatchObject({ login: "missing", source: "unavailable", topLanguages: [] });
});

it("authenticates public profile requests with GITHUB_PUBLIC_TOKEN to lift the rate ceiling (#790)", async () => {
const authHeaders: Array<string | null> = [];
vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => {
authHeaders.push(new Headers(init?.headers).get("authorization"));
const url = input.toString();
if (url.endsWith("/users/dev")) return Response.json({ login: "dev", public_repos: 0, followers: 0 });
return Response.json([]);
});
await fetchPublicContributorProfile("dev", { GITHUB_PUBLIC_TOKEN: "public-token" });
expect(authHeaders).toEqual(["Bearer public-token", "Bearer public-token"]);

authHeaders.length = 0;
await fetchPublicContributorProfile("dev");
expect(authHeaders).toEqual([null, null]);
});
});
Loading