pullfrog 0.1.31 → 0.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -99818,15 +99818,20 @@ var providers = {
99818
99818
  displayName: "Anthropic",
99819
99819
  envVars: ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
99820
99820
  models: {
99821
- // claude-fable-5 is disabled globally access has been revoked, so it
99822
- // can't run as-is anywhere (BYOK, Claude Code, or Router). the fallback
99823
- // redirects all resolution to opus and hides it from pickers; opus is the
99824
- // universally-available flagship, the AUTO_INTELLIGENT tier target, and
99825
- // the recommended pick. clear the fallback if fable access returns (#959).
99821
+ // claude-fable-5 is selectable but not recommended/auto-selected it's
99822
+ // moving to usage-credits-only billing and isn't broadly available yet, so
99823
+ // opus stays the universally-available flagship, the AUTO_INTELLIGENT tier
99824
+ // target, and the recommended pick. an explicit fable pick hits the real
99825
+ // API and is access-gated: it errors for accounts without access today and
99826
+ // just works once usage credits are live — no silent opus fallback, the
99827
+ // API is the source of truth (#959).
99826
99828
  "claude-fable": {
99827
99829
  displayName: "Claude Fable",
99828
99830
  resolve: "anthropic/claude-fable-5",
99829
- fallback: "anthropic/claude-opus",
99831
+ // rolling alias: models.dev's OpenRouter mirror lags brand-new pinned
99832
+ // versions (claude-fable-5 isn't indexed yet), so track ~…-latest to
99833
+ // stay catalog-valid and auto-follow version bumps.
99834
+ openRouterResolve: "openrouter/~anthropic/claude-fable-latest",
99830
99835
  subagentModel: "claude-sonnet"
99831
99836
  },
99832
99837
  "claude-opus": {
@@ -99838,8 +99843,8 @@ var providers = {
99838
99843
  },
99839
99844
  "claude-sonnet": {
99840
99845
  displayName: "Claude Sonnet",
99841
- resolve: "anthropic/claude-sonnet-4-6",
99842
- openRouterResolve: "openrouter/anthropic/claude-sonnet-4.6"
99846
+ resolve: "anthropic/claude-sonnet-5",
99847
+ openRouterResolve: "openrouter/anthropic/claude-sonnet-5"
99843
99848
  },
99844
99849
  "claude-haiku": {
99845
99850
  displayName: "Claude Haiku",
@@ -100017,8 +100022,8 @@ var providers = {
100017
100022
  },
100018
100023
  "claude-sonnet": {
100019
100024
  displayName: "Claude Sonnet",
100020
- resolve: "opencode/claude-sonnet-4-6",
100021
- openRouterResolve: "openrouter/anthropic/claude-sonnet-4.6"
100025
+ resolve: "opencode/claude-sonnet-5",
100026
+ openRouterResolve: "openrouter/anthropic/claude-sonnet-5"
100022
100027
  },
100023
100028
  "claude-haiku": {
100024
100029
  displayName: "Claude Haiku",
@@ -101036,7 +101041,7 @@ var import_semver = __toESM(require_semver2(), 1);
101036
101041
  // package.json
101037
101042
  var package_default = {
101038
101043
  name: "pullfrog",
101039
- version: "0.1.31",
101044
+ version: "0.1.32",
101040
101045
  type: "module",
101041
101046
  bin: {
101042
101047
  pullfrog: "dist/cli.mjs",
@@ -102775,7 +102780,7 @@ function buildAgentsJson() {
102775
102780
  [REVIEWER_AGENT_NAME]: {
102776
102781
  description: "Read-only review subagent for lens-based code review (correctness, security, billing-subsystem, etc.). Reads only \u2014 no writes, no state-changing shell or MCP calls, no nested subagent dispatch.",
102777
102782
  prompt: REVIEWER_SYSTEM_PROMPT,
102778
- model: "claude-sonnet-4-6"
102783
+ model: "claude-sonnet-5"
102779
102784
  }
102780
102785
  };
102781
102786
  return JSON.stringify(agents2);
@@ -150952,6 +150957,34 @@ function getCacheKeyString(value2) {
150952
150957
  );
150953
150958
  }
150954
150959
  }
150960
+ var DEFAULT_RETRY_AFTER_CAP_MS = 2e4;
150961
+ function getErrorResponseHeaders(error49) {
150962
+ if (error49 === null || typeof error49 !== "object") return void 0;
150963
+ const response = error49.response;
150964
+ if (response === null || typeof response !== "object") return void 0;
150965
+ const headers = response.headers;
150966
+ if (headers === null || typeof headers !== "object") return void 0;
150967
+ return headers;
150968
+ }
150969
+ function retryAfterMs(error49) {
150970
+ const headers = getErrorResponseHeaders(error49);
150971
+ if (!headers) return void 0;
150972
+ const retryAfter = headers["retry-after"];
150973
+ if (typeof retryAfter === "string" && retryAfter.trim() !== "") {
150974
+ const seconds = Number(retryAfter);
150975
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
150976
+ const dateMs = Date.parse(retryAfter);
150977
+ if (!Number.isNaN(dateMs)) return Math.max(0, dateMs - Date.now());
150978
+ }
150979
+ if (headers["x-ratelimit-remaining"] === "0") {
150980
+ const reset = headers["x-ratelimit-reset"];
150981
+ if (typeof reset === "string" && reset.trim() !== "") {
150982
+ const resetEpoch = Number(reset);
150983
+ if (Number.isFinite(resetEpoch)) return Math.max(0, resetEpoch * 1e3 - Date.now());
150984
+ }
150985
+ }
150986
+ return void 0;
150987
+ }
150955
150988
  function validateSchema(schema2, value2) {
150956
150989
  const result = schema2["~standard"].validate(value2);
150957
150990
  if (result instanceof Promise) {
@@ -151089,7 +151122,15 @@ function _op(fn2, options) {
151089
151122
  log2.info({ key, error: error49, attempt });
151090
151123
  }
151091
151124
  } else {
151092
- const delay2 = retries[attempt];
151125
+ let delay2 = retries[attempt];
151126
+ if (options.retryAfter) {
151127
+ const extract = options.retryAfter === true ? retryAfterMs : options.retryAfter;
151128
+ const hint = extract(error49);
151129
+ if (hint !== void 0) {
151130
+ const cap = options.retryAfterCap ?? DEFAULT_RETRY_AFTER_CAP_MS;
151131
+ delay2 = Math.max(delay2, Math.min(cap, hint));
151132
+ }
151133
+ }
151093
151134
  if (namePrefix) {
151094
151135
  log2.info(
151095
151136
  `${namePrefix}attempt ${attempt + 1}/${retries.length + 1} failed, retrying in ${delay2}ms`
@@ -156818,6 +156859,12 @@ function isTransientNetworkError(error49, extraPatterns = []) {
156818
156859
  const patterns = ["fetch failed", "ECONNRESET", "ETIMEDOUT", ...extraPatterns];
156819
156860
  return patterns.some((p) => error49.message.includes(p));
156820
156861
  }
156862
+ function isTransientOctokitError(error49) {
156863
+ if (error49 !== null && typeof error49 === "object" && "status" in error49 && typeof error49.status === "number") {
156864
+ return error49.status >= 500;
156865
+ }
156866
+ return isTransientNetworkError(error49);
156867
+ }
156821
156868
 
156822
156869
  // utils/patchWorkflowRunFields.ts
156823
156870
  var STRING_KEYS = [
@@ -156911,6 +156958,195 @@ function aggregateUsage(entries) {
156911
156958
  return out;
156912
156959
  }
156913
156960
 
156961
+ // utils/payload.ts
156962
+ var core4 = __toESM(require_core(), 1);
156963
+ import { readFileSync as readFileSync3 } from "node:fs";
156964
+ import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
156965
+
156966
+ // utils/versioning.ts
156967
+ var import_semver2 = __toESM(require_semver2(), 1);
156968
+ var COMPATIBILITY_POLICY = "non-breaking";
156969
+ function validateCompatibility(payloadVersion, actionVersion) {
156970
+ const payloadSemVer = import_semver2.default.parse(payloadVersion);
156971
+ if (!payloadSemVer)
156972
+ throw new Error(`Payload version ${payloadVersion} is not a valid semantic version.`);
156973
+ const major = payloadSemVer.major;
156974
+ const minor = payloadSemVer.minor;
156975
+ const patch = payloadSemVer.patch;
156976
+ const compatibilityRange = COMPATIBILITY_POLICY === "same-features" ? `^${major}.${minor}.${major === 0 ? patch : 0}` : `^${major}.${major === 0 ? minor : 0}.${major === 0 ? "x" : 0}`;
156977
+ if (!import_semver2.default.satisfies(actionVersion, compatibilityRange)) {
156978
+ throw new Error(
156979
+ `Payload version ${payloadVersion} is incompatible with action version ${actionVersion}. Please update your workflow to use at least ${import_semver2.default.minVersion(compatibilityRange)} version of the action.`
156980
+ );
156981
+ }
156982
+ }
156983
+
156984
+ // utils/payload.ts
156985
+ var ShellPermissionInput = type.enumerated("disabled", "restricted", "enabled");
156986
+ var PushPermissionInput = type.enumerated("disabled", "restricted", "enabled");
156987
+ var StatusChecksInput = type.enumerated("disabled", "enabled");
156988
+ var JsonPayload = type({
156989
+ "~pullfrog": "true",
156990
+ version: "string",
156991
+ "model?": "string | undefined",
156992
+ "modelExplicit?": "boolean | undefined",
156993
+ prompt: "string",
156994
+ "triggerer?": "string | undefined",
156995
+ "baseInstructions?": "string | undefined",
156996
+ "eventInstructions?": "string",
156997
+ "previousRunsNote?": "string",
156998
+ "event?": "object",
156999
+ "xrepo?": type({
157000
+ mode: "'all' | 'explicit'",
157001
+ read: "string[]",
157002
+ write: "string[]",
157003
+ // optional so a payload from an older server build (pre-`unavailable`)
157004
+ // still parses against a newer action across a rolling deploy.
157005
+ "unavailable?": "string[]"
157006
+ }).or("undefined"),
157007
+ "timeout?": "string | undefined",
157008
+ "progressComment?": type({
157009
+ id: "string",
157010
+ type: "'issue' | 'review'"
157011
+ }).or("undefined"),
157012
+ "generateSummary?": "boolean | undefined"
157013
+ });
157014
+ var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
157015
+ function isCollaborator(event) {
157016
+ const perm = event.authorPermission;
157017
+ return perm !== void 0 && COLLABORATOR_PERMISSIONS.includes(perm);
157018
+ }
157019
+ var Inputs = type({
157020
+ "prompt?": type.string.or("undefined"),
157021
+ "prompt_file?": type.string.or("undefined"),
157022
+ "model?": type.string.or("undefined"),
157023
+ "timeout?": type.string.or("undefined"),
157024
+ "push?": PushPermissionInput.or("undefined"),
157025
+ "shell?": ShellPermissionInput.or("undefined"),
157026
+ "status_checks?": StatusChecksInput.or("undefined"),
157027
+ "cwd?": type.string.or("undefined"),
157028
+ "output_schema?": type.string.or("undefined")
157029
+ });
157030
+ function isPayloadEvent(value2) {
157031
+ return typeof value2 === "object" && value2 !== null && "trigger" in value2;
157032
+ }
157033
+ function resolveCwd(cwd) {
157034
+ const workspace = process.env.GITHUB_WORKSPACE;
157035
+ if (!cwd) return workspace;
157036
+ if (isAbsolute2(cwd)) return cwd;
157037
+ return workspace ? resolve2(workspace, cwd) : cwd;
157038
+ }
157039
+ function resolvePromptInput() {
157040
+ const promptInput = core4.getInput("prompt");
157041
+ const promptFile = core4.getInput("prompt_file");
157042
+ if (promptInput && promptFile) {
157043
+ throw new Error("set exactly one of 'prompt' or 'prompt_file' inputs, not both.");
157044
+ }
157045
+ if (promptFile) {
157046
+ return resolvePromptFile(promptFile);
157047
+ }
157048
+ if (!promptInput) {
157049
+ throw new Error("one of 'prompt' or 'prompt_file' inputs is required.");
157050
+ }
157051
+ let parsed2;
157052
+ try {
157053
+ parsed2 = JSON.parse(promptInput);
157054
+ } catch {
157055
+ return promptInput;
157056
+ }
157057
+ if (!parsed2 || typeof parsed2 !== "object" || !("~pullfrog" in parsed2)) {
157058
+ return promptInput;
157059
+ }
157060
+ const jsonPayload = JsonPayload.assert(parsed2);
157061
+ validateCompatibility(jsonPayload.version, package_default.version);
157062
+ return jsonPayload;
157063
+ }
157064
+ function resolvePromptFile(input) {
157065
+ const workspace = process.env.GITHUB_WORKSPACE;
157066
+ const path4 = isAbsolute2(input) ? input : workspace ? resolve2(workspace, input) : resolve2(input);
157067
+ const content = readFileSync3(path4, "utf-8");
157068
+ if (!content.trim()) {
157069
+ throw new Error(`prompt_file ${JSON.stringify(input)} is empty.`);
157070
+ }
157071
+ return content;
157072
+ }
157073
+ function resolveNonPromptInputs() {
157074
+ return Inputs.omit("prompt", "prompt_file").assert({
157075
+ model: core4.getInput("model") || void 0,
157076
+ timeout: core4.getInput("timeout") || void 0,
157077
+ cwd: core4.getInput("cwd") || void 0,
157078
+ push: core4.getInput("push") || void 0,
157079
+ shell: core4.getInput("shell") || void 0,
157080
+ status_checks: core4.getInput("status_checks") || void 0
157081
+ });
157082
+ }
157083
+ var isPullfrog = (actor) => {
157084
+ actor = actor?.replace("[bot]", "");
157085
+ return !!actor && (actor === "pullfrog" || actor === "pullfrogdev");
157086
+ };
157087
+ function resolvePayload(resolvedPromptInput, repoSettings) {
157088
+ const [prompt, jsonPayload] = typeof resolvedPromptInput !== "string" ? [resolvedPromptInput.prompt, resolvedPromptInput] : [resolvedPromptInput, void 0];
157089
+ const inputs = resolveNonPromptInputs();
157090
+ const rawEvent = jsonPayload?.event;
157091
+ const event = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
157092
+ const model = jsonPayload?.model ?? inputs.model ?? repoSettings.model ?? void 0;
157093
+ const isNonCollaborator = !isCollaborator(event);
157094
+ const repoShell = repoSettings.shell ?? "restricted";
157095
+ const inputShell = inputs.shell;
157096
+ let resolvedShell = repoShell;
157097
+ if (inputShell === "disabled") {
157098
+ resolvedShell = "disabled";
157099
+ } else if (inputShell === "restricted" && resolvedShell === "enabled") {
157100
+ resolvedShell = "restricted";
157101
+ }
157102
+ if (isNonCollaborator && resolvedShell === "enabled") {
157103
+ resolvedShell = "restricted";
157104
+ }
157105
+ return {
157106
+ "~pullfrog": true,
157107
+ version: jsonPayload?.version ?? package_default.version,
157108
+ model,
157109
+ // explicit only when the model came from a per-run override flag (carried on
157110
+ // the JSON payload). a GHA `model` input or the repo default is not explicit.
157111
+ modelExplicit: jsonPayload?.modelExplicit ?? false,
157112
+ prompt,
157113
+ triggerer: jsonPayload?.triggerer ?? // it's not a common use case but GITHUB_ACTOR can be a user when the workflow is manually triggered by a user through GitHub Actions UI
157114
+ (!isPullfrog(process.env.GITHUB_ACTOR) ? process.env.GITHUB_ACTOR : void 0),
157115
+ baseInstructions: jsonPayload?.baseInstructions,
157116
+ eventInstructions: jsonPayload?.eventInstructions,
157117
+ previousRunsNote: jsonPayload?.previousRunsNote,
157118
+ event,
157119
+ xrepo: jsonPayload?.xrepo,
157120
+ timeout: inputs.timeout ?? jsonPayload?.timeout,
157121
+ cwd: resolveCwd(inputs.cwd),
157122
+ progressComment: jsonPayload?.progressComment,
157123
+ generateSummary: jsonPayload?.generateSummary,
157124
+ // permissions: inputs > repoSettings > fallbacks
157125
+ push: inputs.push ?? repoSettings.push ?? "restricted",
157126
+ shell: resolvedShell,
157127
+ // opt-in commit-status check-runs (branch protection). workflow-level
157128
+ // static input, off unless the repo's pullfrog.yml sets status_checks: enabled.
157129
+ statusChecks: inputs.status_checks === "enabled",
157130
+ // set by proxy logic in main.ts when routing through OpenRouter
157131
+ proxyModel: void 0
157132
+ };
157133
+ }
157134
+ function resolveOutputSchema() {
157135
+ const raw2 = core4.getInput("output_schema");
157136
+ if (!raw2) return void 0;
157137
+ let parsed2;
157138
+ try {
157139
+ parsed2 = JSON.parse(raw2);
157140
+ } catch {
157141
+ throw new Error(`invalid output_schema: not valid JSON`);
157142
+ }
157143
+ if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) {
157144
+ throw new Error(`invalid output_schema: must be a JSON object`);
157145
+ }
157146
+ log.info("\xBB structured output schema provided \u2014 output will be required");
157147
+ return parsed2;
157148
+ }
157149
+
156914
157150
  // mcp/comment.ts
156915
157151
  function isNotFoundError(error49) {
156916
157152
  return error49 instanceof Error && error49.message.includes("Not Found");
@@ -157332,6 +157568,80 @@ async function buildCommentableMap(ctx, pullNumber) {
157332
157568
  }
157333
157569
  return map2;
157334
157570
  }
157571
+ var OUTSTANDING_THREADS_QUERY = `
157572
+ query ($owner: String!, $name: String!, $prNumber: Int!, $cursor: String) {
157573
+ repository(owner: $owner, name: $name) {
157574
+ pullRequest(number: $prNumber) {
157575
+ reviewThreads(first: 100, after: $cursor) {
157576
+ pageInfo { hasNextPage endCursor }
157577
+ nodes {
157578
+ isResolved
157579
+ comments(first: 1) { nodes { author { login } } }
157580
+ }
157581
+ }
157582
+ }
157583
+ }
157584
+ }
157585
+ `;
157586
+ async function countOutstandingPullfrogThreads(ctx, pullNumber) {
157587
+ let count = 0;
157588
+ let cursor = null;
157589
+ for (let page = 0; page < 50; page += 1) {
157590
+ const response = await ctx.octokit.graphql(
157591
+ OUTSTANDING_THREADS_QUERY,
157592
+ { owner: ctx.repo.owner, name: ctx.repo.name, prNumber: pullNumber, cursor }
157593
+ );
157594
+ const conn = response.repository?.pullRequest?.reviewThreads;
157595
+ for (const thread of conn?.nodes ?? []) {
157596
+ if (!thread || thread.isResolved) continue;
157597
+ const root = thread.comments?.nodes?.find((c) => c != null);
157598
+ if (root && isPullfrog(root.author?.login)) count += 1;
157599
+ }
157600
+ if (!conn?.pageInfo.hasNextPage) break;
157601
+ cursor = conn.pageInfo.endCursor;
157602
+ }
157603
+ return count;
157604
+ }
157605
+ async function approveAfterFix(ctx) {
157606
+ if (ctx.payload.event.trigger !== "fix_review") return;
157607
+ if (!ctx.prApproveEnabled) return;
157608
+ if (ctx.toolState.approval) return;
157609
+ const pullNumber = ctx.payload.event.issue_number;
157610
+ if (typeof pullNumber !== "number") return;
157611
+ const outstanding = await countOutstandingPullfrogThreads(ctx, pullNumber);
157612
+ if (outstanding > 0) {
157613
+ log.info(
157614
+ `skipping fix auto-approval: ${outstanding} unresolved Pullfrog review thread(s) still open on #${pullNumber}`
157615
+ );
157616
+ return;
157617
+ }
157618
+ const pr = await ctx.octokit.rest.pulls.get({
157619
+ owner: ctx.repo.owner,
157620
+ repo: ctx.repo.name,
157621
+ pull_number: pullNumber
157622
+ });
157623
+ if (pr.data.state !== "open") return;
157624
+ const headSha = pr.data.head.sha;
157625
+ const summary2 = ctx.toolState.lastProgressBody?.trim();
157626
+ const body = "> \u2705 Pullfrog addressed all of its review feedback on this PR.\n\n" + (summary2 || "all Pullfrog-raised review threads are resolved \u2014 no outstanding findings remain.");
157627
+ const params = {
157628
+ owner: ctx.repo.owner,
157629
+ repo: ctx.repo.name,
157630
+ pull_number: pullNumber,
157631
+ event: "APPROVE",
157632
+ commit_id: headSha
157633
+ };
157634
+ const result = await createAndSubmitWithFooter(ctx, params, {
157635
+ body,
157636
+ approved: true,
157637
+ hasComments: false
157638
+ });
157639
+ ctx.toolState.approval = { wouldApprove: true, sha: headSha };
157640
+ log.info(`\xBB auto-approved #${pullNumber} after fix run (review ${result.data.id})`);
157641
+ await deleteProgressComment(ctx).catch((err) => {
157642
+ log.debug(`progress comment cleanup after fix auto-approval failed: ${err}`);
157643
+ });
157644
+ }
157335
157645
  function validateInlineComments(comments, map2) {
157336
157646
  const valid = [];
157337
157647
  const dropped = [];
@@ -157422,7 +157732,7 @@ var CreatePullRequestReview = type({
157422
157732
  "1-2 sentence high-level summary with urgency level, critical callouts, and feedback about code outside the diff. Specific feedback on diff lines goes in 'comments' array."
157423
157733
  ).optional(),
157424
157734
  approved: type.boolean.describe(
157425
- "Set to true to submit as an approval. Use for `> \u2705 No new issues found.` reviews where the PR is mergeable as-is and nothing in the body warrants code changes \u2014 approving also suppresses the Fix-button footer affordance so users don't dispatch a fix run on non-actionable feedback. Reserve approved: false for `> \u2139\uFE0F ...` (minor suggestions inline), `> [!IMPORTANT]` (recommended changes), and `> [!CAUTION]` (critical) reviews. Defaults to false (comment-only review). Mutually exclusive with request_changes."
157735
+ "Set to true to submit as an approval. Use for `> \u2705 No new issues found.` reviews where the PR is mergeable as-is and nothing in the body warrants code changes \u2014 approving also suppresses the Fix-button footer affordance so users don't dispatch a fix run on non-actionable feedback. Reserve approved: false for `> \u2139\uFE0F ...` (minor suggestions inline), `> [!IMPORTANT]` (recommended changes), and `> [!CAUTION]` (critical) reviews. Defaults to false (comment-only review). Mutually exclusive with request_changes. Approval is REJECTED while any unresolved Pullfrog review thread remains open on the PR (not just the latest commit's diff): resolve the threads the current code addresses (reply + resolve_review_thread) first, or submit a non-approving review if a real issue remains."
157426
157736
  ).optional(),
157427
157737
  request_changes: type.boolean.describe(
157428
157738
  "Set to true to submit a blocking REQUEST_CHANGES review \u2014 the PR cannot merge until the requested changes are made and the review is dismissed or re-reviewed. Reserve for changes you consider required, not optional suggestions. Mutually exclusive with approved; a contentless request (no body and no comments) is skipped."
@@ -157480,6 +157790,17 @@ function CreatePullRequestReviewTool(ctx) {
157480
157790
  reviewId: dup.reviewId
157481
157791
  };
157482
157792
  }
157793
+ if (approved) {
157794
+ const outstanding = await countOutstandingPullfrogThreads(ctx, pull_number);
157795
+ if (outstanding > 0) {
157796
+ const listRef = formatMcpToolRef(ctx.agentId, "get_review_comments");
157797
+ const resolveRef2 = formatMcpToolRef(ctx.agentId, "resolve_review_thread");
157798
+ throw new Error(
157799
+ `cannot approve: ${outstanding} unresolved Pullfrog review thread(s) still open on this PR. approval requires every prior Pullfrog finding to be resolved, not just the latest commits to be clean. inspect them with \`${listRef}\`; for each thread the current code genuinely addresses, reply then call \`${resolveRef2}\`, and retry this approval. if any thread is a real outstanding issue, do NOT approve \u2014 submit a non-approving review (omit \`approved\`) that covers it instead.`
157800
+ );
157801
+ }
157802
+ }
157803
+ ctx.toolState.approval = { wouldApprove: approved === true, sha: primary.checkoutSha };
157483
157804
  const skip = reviewSkipDecision({
157484
157805
  approved: approved ?? false,
157485
157806
  requestChanges: request_changes ?? false,
@@ -157613,6 +157934,7 @@ function CreatePullRequestReviewTool(ctx) {
157613
157934
  nodeId: reviewNodeId,
157614
157935
  reviewedSha: actuallyReviewedSha
157615
157936
  };
157937
+ if (ctx.toolState.approval) ctx.toolState.approval.sha = actuallyReviewedSha;
157616
157938
  ctx.toolState.wasUpdated = true;
157617
157939
  await deleteProgressComment(ctx).catch((err) => {
157618
157940
  log.debug(`progress comment cleanup after review failed: ${err}`);
@@ -159034,7 +159356,7 @@ function isMetadataYarnClassic(metadataPath) {
159034
159356
  }
159035
159357
 
159036
159358
  // utils/packageManager.ts
159037
- var import_semver2 = __toESM(require_semver2(), 1);
159359
+ var import_semver3 = __toESM(require_semver2(), 1);
159038
159360
  import { existsSync as existsSync5 } from "node:fs";
159039
159361
  import { mkdir, readFile as readFile2 } from "node:fs/promises";
159040
159362
  import { delimiter, join as join15 } from "node:path";
@@ -159056,7 +159378,7 @@ function parsePackageManagerField(value2) {
159056
159378
  return {
159057
159379
  name,
159058
159380
  version: version3,
159059
- concrete: import_semver2.default.valid(version3) !== null,
159381
+ concrete: import_semver3.default.valid(version3) !== null,
159060
159382
  source: "packageManager"
159061
159383
  };
159062
159384
  }
@@ -159070,7 +159392,7 @@ function parseDevEnginesField(field) {
159070
159392
  return {
159071
159393
  name: field.name,
159072
159394
  version: version3,
159073
- concrete: import_semver2.default.valid(version3) !== null,
159395
+ concrete: import_semver3.default.valid(version3) !== null,
159074
159396
  source: "devEngines"
159075
159397
  };
159076
159398
  }
@@ -159104,7 +159426,7 @@ async function resolvePackageManagerSpec(cwd) {
159104
159426
  }
159105
159427
  return devSpec;
159106
159428
  }
159107
- if (pmSpec.concrete && import_semver2.default.satisfies(pmSpec.version, devSpec.version)) {
159429
+ if (pmSpec.concrete && import_semver3.default.satisfies(pmSpec.version, devSpec.version)) {
159108
159430
  return pmSpec;
159109
159431
  }
159110
159432
  if (pmSpec.concrete) {
@@ -159316,8 +159638,15 @@ ${errorMessage}`]
159316
159638
  };
159317
159639
 
159318
159640
  // prep/installPythonDependencies.ts
159319
- import { existsSync as existsSync7 } from "node:fs";
159641
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "node:fs";
159320
159642
  import { join as join17 } from "node:path";
159643
+ function declaresBuildSystem(path4) {
159644
+ return /^\s*\[\s*build-system\s*\]/m.test(readFileSync4(path4, "utf8"));
159645
+ }
159646
+ function configApplies(config3, cwd) {
159647
+ const path4 = join17(cwd, config3.file);
159648
+ return existsSync7(path4) && (config3.applies?.(path4) ?? true);
159649
+ }
159321
159650
  var PYTHON_CONFIGS = [
159322
159651
  {
159323
159652
  file: "requirements.txt",
@@ -159327,7 +159656,8 @@ var PYTHON_CONFIGS = [
159327
159656
  {
159328
159657
  file: "pyproject.toml",
159329
159658
  tool: "pip",
159330
- installCmd: ["pip", "install", "."]
159659
+ installCmd: ["pip", "install", "."],
159660
+ applies: declaresBuildSystem
159331
159661
  },
159332
159662
  {
159333
159663
  file: "Pipfile",
@@ -159389,11 +159719,11 @@ var installPythonDependencies = {
159389
159719
  return false;
159390
159720
  }
159391
159721
  const cwd = process.cwd();
159392
- return PYTHON_CONFIGS.some((config3) => existsSync7(join17(cwd, config3.file)));
159722
+ return PYTHON_CONFIGS.some((config3) => configApplies(config3, cwd));
159393
159723
  },
159394
159724
  run: async (options) => {
159395
159725
  const cwd = process.cwd();
159396
- const config3 = PYTHON_CONFIGS.find((c) => existsSync7(join17(cwd, c.file)));
159726
+ const config3 = PYTHON_CONFIGS.find((c) => configApplies(c, cwd));
159397
159727
  if (!config3) {
159398
159728
  return {
159399
159729
  language: "python",
@@ -161844,12 +162174,17 @@ function resolveModel(ctx) {
161844
162174
  if (envModel) {
161845
162175
  return resolveSlug(envModel) ?? envModel;
161846
162176
  }
161847
- if (ctx.slug) {
161848
- const resolved = resolveSlug(ctx.slug);
162177
+ const slug2 = ctx.slug?.trim();
162178
+ if (slug2) {
162179
+ const resolved = resolveSlug(slug2);
161849
162180
  if (resolved) {
161850
162181
  return resolved;
161851
162182
  }
161852
- log.warning(`\xBB unknown model slug "${ctx.slug}" \u2014 agent will auto-select`);
162183
+ if (slug2.includes("/")) {
162184
+ log.info(`\xBB "${slug2}" is not a curated alias \u2014 passing through as a raw model specifier`);
162185
+ return slug2;
162186
+ }
162187
+ log.warning(`\xBB unknown model slug "${slug2}" \u2014 agent will auto-select`);
161853
162188
  }
161854
162189
  return void 0;
161855
162190
  }
@@ -162412,6 +162747,8 @@ Never use \`sleep\` to wait for commands to complete. Commands run synchronously
162412
162747
 
162413
162748
  When posting comments via ${pullfrogMcpName}, write as a professional team member would. Your final comments should be polished and actionable \u2014 do not include intermediate reasoning like "I'll now look at the code" or "Let me respond to the question."
162414
162749
 
162750
+ Never \`@\`-mention a GitHub username unless that exact handle appears in the user's request or the event context. GitHub already notifies the author and thread participants, so write "the author" or omit it.
162751
+
162415
162752
  When embedding images (e.g. uploaded screenshots) in comments or PR bodies, always use markdown image syntax: \`![description](url)\`. Never paste a naked URL \u2014 it will not render as an image.
162416
162753
 
162417
162754
  ### Progress reporting
@@ -162721,7 +163058,7 @@ function buildModelAccessError(input) {
162721
163058
  }
162722
163059
 
162723
163060
  // utils/normalizeEnv.ts
162724
- var core4 = __toESM(require_core(), 1);
163061
+ var core5 = __toESM(require_core(), 1);
162725
163062
  function sanitizeSecret(key, value2) {
162726
163063
  const trimmed = value2.trim();
162727
163064
  if (trimmed.length === 0) {
@@ -162735,7 +163072,7 @@ function sanitizeSecret(key, value2) {
162735
163072
  `\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
162736
163073
  );
162737
163074
  }
162738
- core4.setSecret(trimmed);
163075
+ core5.setSecret(trimmed);
162739
163076
  return trimmed;
162740
163077
  }
162741
163078
  function normalizeEnv() {
@@ -162779,7 +163116,7 @@ function normalizeEnv() {
162779
163116
  }
162780
163117
 
162781
163118
  // utils/overrides.ts
162782
- var core5 = __toESM(require_core(), 1);
163119
+ var core6 = __toESM(require_core(), 1);
162783
163120
  var DENIED_OVERRIDE_NAMES = /* @__PURE__ */ new Set([
162784
163121
  "GITHUB_TOKEN",
162785
163122
  "GH_TOKEN",
@@ -162825,7 +163162,7 @@ function applyOverrides(params) {
162825
163162
  denied.push(key);
162826
163163
  continue;
162827
163164
  }
162828
- if (value2.length > 0) core5.setSecret(value2);
163165
+ if (value2.length > 0) core6.setSecret(value2);
162829
163166
  params.env[key] = value2;
162830
163167
  applied.push(key);
162831
163168
  }
@@ -162833,168 +163170,6 @@ function applyOverrides(params) {
162833
163170
  return { applied, denied };
162834
163171
  }
162835
163172
 
162836
- // utils/payload.ts
162837
- var core6 = __toESM(require_core(), 1);
162838
- import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
162839
-
162840
- // utils/versioning.ts
162841
- var import_semver3 = __toESM(require_semver2(), 1);
162842
- var COMPATIBILITY_POLICY = "non-breaking";
162843
- function validateCompatibility(payloadVersion, actionVersion) {
162844
- const payloadSemVer = import_semver3.default.parse(payloadVersion);
162845
- if (!payloadSemVer)
162846
- throw new Error(`Payload version ${payloadVersion} is not a valid semantic version.`);
162847
- const major = payloadSemVer.major;
162848
- const minor = payloadSemVer.minor;
162849
- const patch = payloadSemVer.patch;
162850
- const compatibilityRange = COMPATIBILITY_POLICY === "same-features" ? `^${major}.${minor}.${major === 0 ? patch : 0}` : `^${major}.${major === 0 ? minor : 0}.${major === 0 ? "x" : 0}`;
162851
- if (!import_semver3.default.satisfies(actionVersion, compatibilityRange)) {
162852
- throw new Error(
162853
- `Payload version ${payloadVersion} is incompatible with action version ${actionVersion}. Please update your workflow to use at least ${import_semver3.default.minVersion(compatibilityRange)} version of the action.`
162854
- );
162855
- }
162856
- }
162857
-
162858
- // utils/payload.ts
162859
- var ShellPermissionInput = type.enumerated("disabled", "restricted", "enabled");
162860
- var PushPermissionInput = type.enumerated("disabled", "restricted", "enabled");
162861
- var JsonPayload = type({
162862
- "~pullfrog": "true",
162863
- version: "string",
162864
- "model?": "string | undefined",
162865
- "modelExplicit?": "boolean | undefined",
162866
- prompt: "string",
162867
- "triggerer?": "string | undefined",
162868
- "baseInstructions?": "string | undefined",
162869
- "eventInstructions?": "string",
162870
- "previousRunsNote?": "string",
162871
- "event?": "object",
162872
- "xrepo?": type({
162873
- mode: "'all' | 'explicit'",
162874
- read: "string[]",
162875
- write: "string[]",
162876
- // optional so a payload from an older server build (pre-`unavailable`)
162877
- // still parses against a newer action across a rolling deploy.
162878
- "unavailable?": "string[]"
162879
- }).or("undefined"),
162880
- "timeout?": "string | undefined",
162881
- "progressComment?": type({
162882
- id: "string",
162883
- type: "'issue' | 'review'"
162884
- }).or("undefined"),
162885
- "generateSummary?": "boolean | undefined"
162886
- });
162887
- var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
162888
- function isCollaborator(event) {
162889
- const perm = event.authorPermission;
162890
- return perm !== void 0 && COLLABORATOR_PERMISSIONS.includes(perm);
162891
- }
162892
- var Inputs = type({
162893
- prompt: "string",
162894
- "model?": type.string.or("undefined"),
162895
- "timeout?": type.string.or("undefined"),
162896
- "push?": PushPermissionInput.or("undefined"),
162897
- "shell?": ShellPermissionInput.or("undefined"),
162898
- "cwd?": type.string.or("undefined"),
162899
- "output_schema?": type.string.or("undefined")
162900
- });
162901
- function isPayloadEvent(value2) {
162902
- return typeof value2 === "object" && value2 !== null && "trigger" in value2;
162903
- }
162904
- function resolveCwd(cwd) {
162905
- const workspace = process.env.GITHUB_WORKSPACE;
162906
- if (!cwd) return workspace;
162907
- if (isAbsolute2(cwd)) return cwd;
162908
- return workspace ? resolve2(workspace, cwd) : cwd;
162909
- }
162910
- function resolvePromptInput() {
162911
- const prompt = core6.getInput("prompt", { required: true });
162912
- let parsed2;
162913
- try {
162914
- parsed2 = JSON.parse(prompt);
162915
- } catch {
162916
- return prompt;
162917
- }
162918
- if (!parsed2 || typeof parsed2 !== "object" || !("~pullfrog" in parsed2)) {
162919
- return prompt;
162920
- }
162921
- const jsonPayload = JsonPayload.assert(parsed2);
162922
- validateCompatibility(jsonPayload.version, package_default.version);
162923
- return jsonPayload;
162924
- }
162925
- function resolveNonPromptInputs() {
162926
- return Inputs.omit("prompt").assert({
162927
- model: core6.getInput("model") || void 0,
162928
- timeout: core6.getInput("timeout") || void 0,
162929
- cwd: core6.getInput("cwd") || void 0,
162930
- push: core6.getInput("push") || void 0,
162931
- shell: core6.getInput("shell") || void 0
162932
- });
162933
- }
162934
- var isPullfrog = (actor) => {
162935
- actor = actor?.replace("[bot]", "");
162936
- return !!actor && (actor === "pullfrog" || actor === "pullfrogdev");
162937
- };
162938
- function resolvePayload(resolvedPromptInput, repoSettings) {
162939
- const [prompt, jsonPayload] = typeof resolvedPromptInput !== "string" ? [resolvedPromptInput.prompt, resolvedPromptInput] : [resolvedPromptInput, void 0];
162940
- const inputs = resolveNonPromptInputs();
162941
- const rawEvent = jsonPayload?.event;
162942
- const event = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
162943
- const model = jsonPayload?.model ?? inputs.model ?? repoSettings.model ?? void 0;
162944
- const isNonCollaborator = !isCollaborator(event);
162945
- const repoShell = repoSettings.shell ?? "restricted";
162946
- const inputShell = inputs.shell;
162947
- let resolvedShell = repoShell;
162948
- if (inputShell === "disabled") {
162949
- resolvedShell = "disabled";
162950
- } else if (inputShell === "restricted" && resolvedShell === "enabled") {
162951
- resolvedShell = "restricted";
162952
- }
162953
- if (isNonCollaborator && resolvedShell === "enabled") {
162954
- resolvedShell = "restricted";
162955
- }
162956
- return {
162957
- "~pullfrog": true,
162958
- version: jsonPayload?.version ?? package_default.version,
162959
- model,
162960
- // explicit only when the model came from a per-run override flag (carried on
162961
- // the JSON payload). a GHA `model` input or the repo default is not explicit.
162962
- modelExplicit: jsonPayload?.modelExplicit ?? false,
162963
- prompt,
162964
- triggerer: jsonPayload?.triggerer ?? // it's not a common use case but GITHUB_ACTOR can be a user when the workflow is manually triggered by a user through GitHub Actions UI
162965
- (!isPullfrog(process.env.GITHUB_ACTOR) ? process.env.GITHUB_ACTOR : void 0),
162966
- baseInstructions: jsonPayload?.baseInstructions,
162967
- eventInstructions: jsonPayload?.eventInstructions,
162968
- previousRunsNote: jsonPayload?.previousRunsNote,
162969
- event,
162970
- xrepo: jsonPayload?.xrepo,
162971
- timeout: inputs.timeout ?? jsonPayload?.timeout,
162972
- cwd: resolveCwd(inputs.cwd),
162973
- progressComment: jsonPayload?.progressComment,
162974
- generateSummary: jsonPayload?.generateSummary,
162975
- // permissions: inputs > repoSettings > fallbacks
162976
- push: inputs.push ?? repoSettings.push ?? "restricted",
162977
- shell: resolvedShell,
162978
- // set by proxy logic in main.ts when routing through OpenRouter
162979
- proxyModel: void 0
162980
- };
162981
- }
162982
- function resolveOutputSchema() {
162983
- const raw2 = core6.getInput("output_schema");
162984
- if (!raw2) return void 0;
162985
- let parsed2;
162986
- try {
162987
- parsed2 = JSON.parse(raw2);
162988
- } catch {
162989
- throw new Error(`invalid output_schema: not valid JSON`);
162990
- }
162991
- if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) {
162992
- throw new Error(`invalid output_schema: must be a JSON object`);
162993
- }
162994
- log.info("\xBB structured output schema provided \u2014 output will be required");
162995
- return parsed2;
162996
- }
162997
-
162998
163173
  // utils/proxy.ts
162999
163174
  var core8 = __toESM(require_core(), 1);
163000
163175
 
@@ -163145,7 +163320,10 @@ async function resolveTokens(params) {
163145
163320
  contents: "write",
163146
163321
  pull_requests: "write",
163147
163322
  issues: "write",
163148
- checks: "read",
163323
+ // write (not read) so the run can post `pullfrog` / `pullfrog-approval`
163324
+ // commit-status check-runs for branch protection. the app already grants
163325
+ // checks:write; this scopes the MCP token up to use it.
163326
+ checks: "write",
163149
163327
  actions: "read"
163150
163328
  };
163151
163329
  const mcpToken = await acquireNewToken({ repos: writeRepos, permissions: mcpPermissions });
@@ -163254,7 +163432,9 @@ async function resolveTokens(params) {
163254
163432
  };
163255
163433
  const removeSignalHandler = onExitSignal(dispose);
163256
163434
  return {
163257
- gitToken,
163435
+ get gitToken() {
163436
+ return currentGitToken;
163437
+ },
163258
163438
  mcpToken,
163259
163439
  readToken,
163260
163440
  refreshGitToken,
@@ -163694,7 +163874,11 @@ async function resolveRunContextData(params) {
163694
163874
  } catch {
163695
163875
  }
163696
163876
  const [repoResponse, runContext] = await Promise.all([
163697
- params.octokit.repos.get({ owner: repoContext.owner, repo: repoContext.name }),
163877
+ op(() => params.octokit.repos.get({ owner: repoContext.owner, repo: repoContext.name }), {
163878
+ name: "repos.get",
163879
+ retries: [100, 500],
163880
+ bail: (error49) => !isTransientOctokitError(error49)
163881
+ })(),
163698
163882
  fetchRunContext({ token: params.token, repoContext, oidcToken })
163699
163883
  ]);
163700
163884
  return {
@@ -163979,13 +164163,20 @@ async function dispatchFollowUpReReview(ctx, reviewedSha) {
163979
164163
  eventInstructions: RE_REVIEW_PREAMBLE,
163980
164164
  event
163981
164165
  };
163982
- await ctx.octokit.rest.actions.createWorkflowDispatch({
163983
- owner: ctx.repo.owner,
163984
- repo: ctx.repo.name,
163985
- workflow_id: getCurrentWorkflowFilename(),
163986
- ref: pr.data.base.repo.default_branch,
163987
- inputs: { prompt: JSON.stringify(payload) }
163988
- });
164166
+ await op(
164167
+ () => ctx.octokit.rest.actions.createWorkflowDispatch({
164168
+ owner: ctx.repo.owner,
164169
+ repo: ctx.repo.name,
164170
+ workflow_id: getCurrentWorkflowFilename(),
164171
+ ref: pr.data.base.repo.default_branch,
164172
+ inputs: { prompt: JSON.stringify(payload) }
164173
+ }),
164174
+ {
164175
+ name: "reReviewDispatch",
164176
+ retries: [500, 2e3],
164177
+ bail: (error49) => !isTransientOctokitError(error49)
164178
+ }
164179
+ )();
163989
164180
  }
163990
164181
  function getCurrentWorkflowFilename() {
163991
164182
  const ref = process.env.GITHUB_WORKFLOW_REF ?? "";
@@ -163993,6 +164184,62 @@ function getCurrentWorkflowFilename() {
163993
164184
  return match3?.[1] ?? "pullfrog.yml";
163994
164185
  }
163995
164186
 
164187
+ // utils/statusChecks.ts
164188
+ var COMPLETION_CHECK = "pullfrog";
164189
+ var APPROVAL_CHECK = "pullfrog-approval";
164190
+ async function createCheckRun(ctx, params) {
164191
+ const createParams = {
164192
+ owner: ctx.repo.owner,
164193
+ repo: ctx.repo.name,
164194
+ name: params.name,
164195
+ head_sha: params.headSha,
164196
+ status: "completed",
164197
+ conclusion: params.conclusion,
164198
+ output: { title: params.title, summary: params.summary }
164199
+ };
164200
+ if (ctx.runId) {
164201
+ createParams.details_url = `https://github.com/${ctx.repo.owner}/${ctx.repo.name}/actions/runs/${ctx.runId}`;
164202
+ }
164203
+ await ctx.octokit.rest.checks.create(createParams);
164204
+ log.info(`\xBB posted ${params.name} check (${params.conclusion}) on ${params.headSha.slice(0, 7)}`);
164205
+ }
164206
+ async function reportStatusChecks(ctx, params) {
164207
+ if (!ctx.payload.statusChecks) return;
164208
+ const event = ctx.payload.event;
164209
+ const pullNumber = event.issue_number;
164210
+ if (event.is_pr !== true || typeof pullNumber !== "number") return;
164211
+ let headSha;
164212
+ try {
164213
+ const pr = await ctx.octokit.rest.pulls.get({
164214
+ owner: ctx.repo.owner,
164215
+ repo: ctx.repo.name,
164216
+ pull_number: pullNumber
164217
+ });
164218
+ headSha = pr.data.head.sha;
164219
+ } catch (err) {
164220
+ log.debug(`status checks: failed to resolve PR #${pullNumber} head sha: ${err}`);
164221
+ return;
164222
+ }
164223
+ const completionSha = primaryRepoState(ctx.toolState).checkoutSha ?? headSha;
164224
+ await createCheckRun(ctx, {
164225
+ name: COMPLETION_CHECK,
164226
+ headSha: completionSha,
164227
+ conclusion: params.runSucceeded ? "success" : "failure",
164228
+ title: params.runSucceeded ? "Pullfrog run completed" : "Pullfrog run failed",
164229
+ summary: params.runSucceeded ? "The Pullfrog run finished successfully." : "The Pullfrog run failed or timed out. See the run logs for details."
164230
+ }).catch((err) => log.debug(`status checks: ${COMPLETION_CHECK} post failed: ${err}`));
164231
+ const approval = ctx.toolState.approval;
164232
+ if (params.runSucceeded && approval) {
164233
+ await createCheckRun(ctx, {
164234
+ name: APPROVAL_CHECK,
164235
+ headSha: approval.sha ?? headSha,
164236
+ conclusion: approval.wouldApprove ? "success" : "failure",
164237
+ title: approval.wouldApprove ? "Pullfrog would approve" : "Pullfrog would not approve",
164238
+ summary: approval.wouldApprove ? "Pullfrog has no outstanding review feedback on this PR." : "Pullfrog has outstanding review feedback or requested changes on this PR."
164239
+ }).catch((err) => log.debug(`status checks: ${APPROVAL_CHECK} post failed: ${err}`));
164240
+ }
164241
+ }
164242
+
163996
164243
  // utils/runLifecycle.ts
163997
164244
  async function persistRunArtifacts(toolContext) {
163998
164245
  await postReviewCleanup(toolContext).catch((error49) => {
@@ -164036,6 +164283,12 @@ async function finalizeSuccessRun(input) {
164036
164283
  log.info(`::pullfrog-output::${Buffer.from(input.toolState.output).toString("base64")}`);
164037
164284
  core10.setOutput("result", input.toolState.output);
164038
164285
  }
164286
+ if (input.result.success) {
164287
+ await approveAfterFix(input.toolContext).catch((error49) => {
164288
+ log.debug(`fix auto-approval failed: ${error49}`);
164289
+ });
164290
+ }
164291
+ await reportStatusChecks(input.toolContext, { runSucceeded: input.result.success });
164039
164292
  }
164040
164293
  async function writeRunErrorOutputs(input) {
164041
164294
  try {
@@ -164462,7 +164715,11 @@ async function main() {
164462
164715
  get githubInstallationToken() {
164463
164716
  return getGitHubInstallationToken();
164464
164717
  },
164465
- gitToken: tokenRef.gitToken,
164718
+ // live getter, same reason as #891 above — reads the current git token
164719
+ // (canonical rationale on TokenRef.gitToken). see #964.
164720
+ get gitToken() {
164721
+ return tokenRef.gitToken;
164722
+ },
164466
164723
  refreshGitToken: tokenRef.refreshGitToken,
164467
164724
  readToken: tokenRef.readToken,
164468
164725
  xrepo: payload.xrepo,
@@ -164712,6 +164969,7 @@ ${instructions.user}` : null,
164712
164969
  await writeRunErrorOutputs({ rendered, toolState });
164713
164970
  if (toolContext) {
164714
164971
  await persistRunArtifacts(toolContext);
164972
+ await reportStatusChecks(toolContext, { runSucceeded: false });
164715
164973
  }
164716
164974
  return {
164717
164975
  success: false,