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`);
 

Reply via email to