This is an automated email from the ASF dual-hosted git repository. spmallette pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit cc2b500bf00e89af621661fc5f37c6991a1d0a23 Author: Stephen Mallette <[email protected]> AuthorDate: Fri Oct 9 15:44:05 2026 -0400 Fetch PR comments in tinker-review GitHub rejected the comment requests for lacking a User-Agent, so every review reported none. Requests now send one and use a gh token when available, and a failed fetch is logged instead of read as no comments. Assisted-by: Claude Code:claude-opus-5-5 --- .skills/tinker-review/references/interfaces.md | 3 +- .../tinker-review/scripts/discovery/discussions.js | 77 +++++++++++++--------- .skills/tinker-review/scripts/review.js | 2 +- 3 files changed, 50 insertions(+), 32 deletions(-) diff --git a/.skills/tinker-review/references/interfaces.md b/.skills/tinker-review/references/interfaces.md index c78eed686b..ed88e3fc48 100644 --- a/.skills/tinker-review/references/interfaces.md +++ b/.skills/tinker-review/references/interfaces.md @@ -133,7 +133,8 @@ interface Evidence { orphans: OrphanResult; }; discussions: DiscussionsResult & { beads: BeadsDiscovery }; - // jiras[], devList[], secondary[], proposals[], prComments{}; beads = the roots + // jiras[], devList[], secondary[], proposals[], prComments{}, prCommentsError (null, or + // why the GitHub fetch failed — then the empty prComments mean nothing); beads = the roots // reached from record beads naming the PR or those discussions, their subtrees, // and whether bd dolt pull succeeded changedFiles: string[]; diff --git a/.skills/tinker-review/scripts/discovery/discussions.js b/.skills/tinker-review/scripts/discovery/discussions.js index eafa49263b..1fd5ebc9b5 100644 --- a/.skills/tinker-review/scripts/discovery/discussions.js +++ b/.skills/tinker-review/scripts/discovery/discussions.js @@ -18,6 +18,8 @@ */ import { get } from "node:https"; +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; import { readdir, readFile } from "node:fs/promises"; import { join, basename } from "node:path"; @@ -27,11 +29,13 @@ const TINKERPOP_JIRA_PATTERN = /TINKERPOP-(\d+)/g; const DEV_LIST_LINK_PATTERN = /https?:\/\/lists\.apache\.org\/[^\s)\]>"[]*/g; const PROPOSAL_DIR = "docs/src/dev/future"; -function httpGet(url) { +// GitHub rejects any request without a User-Agent (403), so every request +// carries one. +function httpGet(url, headers = {}) { return new Promise((resolve, reject) => { - get(url, (res) => { + get(url, { headers: { "User-Agent": "tinker-review", ...headers } }, (res) => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { - return httpGet(res.headers.location).then(resolve, reject); + return httpGet(res.headers.location, headers).then(resolve, reject); } let data = ""; res.on("data", (chunk) => { data += chunk; }); @@ -70,35 +74,45 @@ async function fetchJira(ticketId) { } } +// GitHub API headers. A token (GITHUB_TOKEN, GH_TOKEN, or the gh CLI's login) +// lifts the 60-requests-an-hour limit on unauthenticated calls; without one the +// calls still work, just rate-limited. +let githubHeadersPromise; +function githubHeaders() { + githubHeadersPromise ||= (async () => { + const headers = { Accept: "application/vnd.github+json" }; + let token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN; + if (!token) { + token = await promisify(execFile)("gh", ["auth", "token"]) + .then((r) => r.stdout.trim()) + .catch(() => ""); + } + if (token) headers.Authorization = `Bearer ${token}`; + return headers; + })(); + return githubHeadersPromise; +} + +// Throws on failure, so a failed fetch is reported rather than read as "no comments". async function fetchPrComments(prNumber) { - try { - const url = `https://api.github.com/repos/apache/tinkerpop/issues/${prNumber}/comments?per_page=50`; - const data = JSON.parse(await httpGet(url)); - if (!Array.isArray(data)) return []; - return data.map((c) => ({ - author: c.user?.login || "unknown", - body: (c.body || "").slice(0, 1000), - timestamp: c.created_at, - })); - } catch { - return []; - } + const url = `https://api.github.com/repos/apache/tinkerpop/issues/${prNumber}/comments?per_page=100`; + const data = JSON.parse(await httpGet(url, await githubHeaders())); + return data.map((c) => ({ + author: c.user?.login || "unknown", + body: (c.body || "").slice(0, 1000), + timestamp: c.created_at, + })); } async function fetchPrReviewComments(prNumber) { - try { - const url = `https://api.github.com/repos/apache/tinkerpop/pulls/${prNumber}/comments?per_page=50`; - const data = JSON.parse(await httpGet(url)); - if (!Array.isArray(data)) return []; - return data.map((c) => ({ - author: c.user?.login || "unknown", - body: (c.body || "").slice(0, 1000), - path: c.path || "", - timestamp: c.created_at, - })); - } catch { - return []; - } + const url = `https://api.github.com/repos/apache/tinkerpop/pulls/${prNumber}/comments?per_page=100`; + const data = JSON.parse(await httpGet(url, await githubHeaders())); + return data.map((c) => ({ + author: c.user?.login || "unknown", + body: (c.body || "").slice(0, 1000), + path: c.path || "", + timestamp: c.created_at, + })); } async function searchDevList(keywords) { @@ -282,8 +296,10 @@ export async function discoverDiscussions(params) { const { pr, prTitle = "", prBody = "", diff = "", keywords = [], repoPath = "" } = params; // Fetch PR comments from GitHub (issue comments + inline review comments) - const issueComments = pr ? await fetchPrComments(pr) : []; - const reviewComments = pr ? await fetchPrReviewComments(pr) : []; + const prCommentErrors = []; + const failed = (kind) => (err) => { prCommentErrors.push(`${kind}: ${err.message}`); return []; }; + const issueComments = pr ? await fetchPrComments(pr).catch(failed("issue comments")) : []; + const reviewComments = pr ? await fetchPrReviewComments(pr).catch(failed("review comments")) : []; const prCommentBodies = [...issueComments, ...reviewComments].map((c) => c.body); const allText = [prTitle, prBody, ...prCommentBodies, diff].join("\n"); @@ -341,6 +357,7 @@ export async function discoverDiscussions(params) { secondary: secondaryDiscussions, prComments: { issue: issueComments, review: reviewComments }, + prCommentsError: prCommentErrors.length > 0 ? prCommentErrors.join("; ") : null, proposals, proposalLinked: proposalLinks.length > 0, diff --git a/.skills/tinker-review/scripts/review.js b/.skills/tinker-review/scripts/review.js index fc56951997..905004268e 100644 --- a/.skills/tinker-review/scripts/review.js +++ b/.skills/tinker-review/scripts/review.js @@ -415,7 +415,7 @@ export async function phase1(session) { repoPath, }); log(` jiras: ${discussions.jiras.length} found${discussions.jiraMissing ? " (none referenced)" : ""}`); - log(` pr comments: ${discussions.prComments.issue.length} issue + ${discussions.prComments.review.length} review`); + log(` pr comments: ${discussions.prComments.issue.length} issue + ${discussions.prComments.review.length} review${discussions.prCommentsError ? ` (FETCH FAILED — ${discussions.prCommentsError})` : ""}`); log(` dev list: ${discussions.devList.length} found${discussions.devListSearchPerformed ? ` (searched: ${prKeywords.join(", ")})` : ""}`); log(` proposals: ${discussions.proposals.length} found`);
