Code quality sweep: 30+ bug fixes, security hardening, and UX improvements (#507)

* Update waitlist, run ralph experiments

* fix PR files pagination: use octokit.paginate() for >100 files

* fix garbled FAQ answer on landing page

* track cache read/write tokens in OpenCode agent usage

* wrap dispatch() calls in try/catch to prevent webhook retries on transient failures

* replace raw error messages with generic responses in API routes

* guard request.json() calls with try-catch returning 400 on malformed bodies

* log warning when GraphQL review thread/comment counts hit pagination limits

* reduce review comment cache TTL from 24 hours to 10 minutes

* use select instead of include for proxyKey in workflow run queries

* align Claude agent activity timeout to 5 minutes to match OpenCode agent

* add in-memory dedup for PR close webhooks to prevent duplicate indexing

* extract isPullfrogLogin() helper for shared Pullfrog detection logic

* check response.ok on log fetch in checkSuite.ts

* add 10s timeouts to checkSuite API calls and log fetch

* parallelize proxy key usage API calls with Promise.allSettled

* fix three typos on landing page: colleage, dectects, reponse

* move MAX_STDERR_LINES constant to shared.ts

* add indexes on Repo.accountId and PFUser.accountId FK columns

* remove unused Permission enum from Prisma schema

* populate author and keywords in action/package.json

* use crypto.timingSafeEqual for all secret comparisons

* add missing env vars to globals.ts: R2, webhook, and API secrets

* remove commented-out UserRepo model from Prisma schema

* replace console.log/error with log utility in production API routes

* replace catch(error: any) with proper type guards in getUserRole

* remove stale TODO comment on console page

* handle repository_transferred webhook to update owner

* show toast.error instead of console.error on mode/workflow mutation failures

* add Space key handler for keyboard navigation on workflow run links

* replace role=link spans with button elements for proper accessibility

* add root 404 page with Pullfrog branding

* update ISSUES.md: mark completed items

* mark remaining low-priority UX items as addressed

* add error logging alongside toasts, add check script, update ralph commands

* address review feedback: squash migrations, fix try/catch scope, wire up globals consumers

- squash drop_permission_enum migration into add_indexes migration (one migration per PR)
- move getPullRequest() outside try/catch in mention handler so errors aren't mislogged as "dispatch failed"
- restore key ID in proxyKeys.ts Promise.allSettled error log
- remove accidental asdf.txt and ralph.md files
- wire up globals.ts exports to consumers (r2-uploads, r2-private, verifyHookdeckSignature, sync-usage, forwardPreviewWebhook, dispatch-workflow)

Made-with: Cursor

* update model snapshot (qwen3.6-plus-preview renamed to qwen3.6-plus)

Made-with: Cursor
This commit is contained in:
Colin McDonnell
2026-04-02 21:02:38 +00:00
committed by pullfrog[bot]
parent f82f08dff6
commit 36cc5cde14
9 changed files with 119 additions and 37 deletions
+9 -3
View File
@@ -24,7 +24,13 @@ import { spawn } from "../utils/subprocess.ts";
import { ThinkingTimer } from "../utils/timer.ts";
import type { TodoTracker } from "../utils/todoTracking.ts";
import { getDevDependencyVersion } from "../utils/version.ts";
import { type AgentResult, type AgentRunContext, type AgentUsage, agent } from "./shared.ts";
import {
type AgentResult,
type AgentRunContext,
type AgentUsage,
agent,
MAX_STDERR_LINES,
} from "./shared.ts";
async function installClaudeCli(): Promise<string> {
return await installFromNpmTarball({
@@ -311,7 +317,7 @@ async function runClaude(params: RunParams): Promise<AgentResult> {
};
const recentStderr: string[] = [];
const MAX_STDERR_LINES = 20;
let lastProviderError: string | null = null;
let output = "";
@@ -323,7 +329,7 @@ async function runClaude(params: RunParams): Promise<AgentResult> {
args: params.args,
cwd: params.cwd,
env: params.env,
activityTimeout: 0,
activityTimeout: 300_000,
stdio: ["ignore", "pipe", "pipe"],
onStdout: async (chunk) => {
const text = chunk.toString();
+18 -6
View File
@@ -25,7 +25,13 @@ import { spawn } from "../utils/subprocess.ts";
import { ThinkingTimer } from "../utils/timer.ts";
import type { TodoTracker } from "../utils/todoTracking.ts";
import { getDevDependencyVersion } from "../utils/version.ts";
import { type AgentResult, type AgentRunContext, type AgentUsage, agent } from "./shared.ts";
import {
type AgentResult,
type AgentRunContext,
type AgentUsage,
agent,
MAX_STDERR_LINES,
} from "./shared.ts";
async function installOpencodeCli(): Promise<string> {
return await installFromNpmTarball({
@@ -255,7 +261,7 @@ async function runOpenCode(params: RunParams): Promise<AgentResult> {
const thinkingTimer = new ThinkingTimer();
let finalOutput = "";
let accumulatedTokens = { input: 0, output: 0 };
let accumulatedTokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
let tokensLogged = false;
const toolCallTimings = new Map<string, number>();
let currentStepId: string | null = null;
@@ -263,11 +269,15 @@ async function runOpenCode(params: RunParams): Promise<AgentResult> {
let stepHistory: Array<{ stepId: string; stepType: string; toolCalls: string[] }> = [];
function buildUsage(): AgentUsage | undefined {
return accumulatedTokens.input > 0 || accumulatedTokens.output > 0
const totalInput =
accumulatedTokens.input + accumulatedTokens.cacheRead + accumulatedTokens.cacheWrite;
return totalInput > 0 || accumulatedTokens.output > 0
? {
agent: "pullfrog",
inputTokens: accumulatedTokens.input,
inputTokens: totalInput,
outputTokens: accumulatedTokens.output,
cacheReadTokens: accumulatedTokens.cacheRead || undefined,
cacheWriteTokens: accumulatedTokens.cacheWrite || undefined,
}
: undefined;
}
@@ -279,7 +289,7 @@ async function runOpenCode(params: RunParams): Promise<AgentResult> {
);
log.debug(`» ${params.label} init event (full): ${JSON.stringify(event)}`);
finalOutput = "";
accumulatedTokens = { input: 0, output: 0 };
accumulatedTokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
tokensLogged = false;
},
message: (event: OpenCodeMessageEvent) => {
@@ -321,6 +331,8 @@ async function runOpenCode(params: RunParams): Promise<AgentResult> {
if (eventTokens) {
accumulatedTokens.input += eventTokens.input || 0;
accumulatedTokens.output += eventTokens.output || 0;
accumulatedTokens.cacheRead += eventTokens.cache?.read || 0;
accumulatedTokens.cacheWrite += eventTokens.cache?.write || 0;
}
if (currentStepId === stepId) {
currentStepId = null;
@@ -425,7 +437,7 @@ async function runOpenCode(params: RunParams): Promise<AgentResult> {
};
const recentStderr: string[] = [];
const MAX_STDERR_LINES = 20;
let lastProviderError: string | null = null;
let output = "";
+3
View File
@@ -3,6 +3,9 @@ import type { ResolvedInstructions } from "../utils/instructions.ts";
import type { ResolvedPayload } from "../utils/payload.ts";
import type { TodoTracker } from "../utils/todoTracking.ts";
// maximum number of stderr lines to keep in the rolling buffer during agent execution
export const MAX_STDERR_LINES = 20;
/**
* token/cost usage data from a single agent run
*/