codeflash-internal/js/cf-api/endpoints/suggest-pr-changes.ts
Kevin Turcios d7a8b8f227
perf: fix CI build + lazy-load heavy libs + parallelize DB queries (#2601)
## Summary
- **Fix CI build failure**: Auth0Client crashes during Next.js
prerendering when env vars aren't set. Returns a no-op stub (`getSession
→ null`) when domain is missing — semantically correct for static
generation
- **Lazy-load markdown libs (~260kb)**: ReactMarkdown, remarkGfm, and
react-syntax-highlighter were eagerly imported in monaco-diff-viewer but
only rendered when user expands "Generated Tests". Extracted into a
dynamic component
- **Parallelize repo detail query**: `getRepositoryById` ran the
activity count sequentially after the repo lookup. Since `repoId` is
already available, all three queries now run in parallel

## Test plan
- [ ] CI `build` check passes (was failing since #2598)
- [ ] Trace page still renders generated tests correctly when expanded
- [ ] Repository detail page loads correctly with activity status
2026-04-13 11:03:05 -05:00

962 lines
28 KiB
TypeScript

import { determineValidHunks, fileDiffsToMap, isDiffContentsWellFormed } from "../diff_utils.js"
import { userNickname } from "../auth0-mgmt.js"
import { logger } from "../utils/logger.js"
import { getInstallationOctokitByOwner, isUserCollaborator } from "../github/github-utils.js"
import { githubApp } from "../github/github-app.js"
import {
buildDependentPrTitle,
buildPrCommentBody,
generateOptimizationReviewTemplate,
originalPRComment,
} from "../github/pr-changes-utils.js"
import {
createDependentPullRequest,
createNewBranchFromDiffContents,
} from "../github/create-pr-from-diffcontents.js"
import { posthog } from "../analytics.js"
import type { FileDiffContent } from "@codeflash-ai/code-suggester/build/src/types.js"
import { PrismaClient } from "@prisma/client"
import { prisma } from "@codeflash-ai/common"
import { sendSlackMessage } from "../github/slack_util.js"
import { Response } from "express"
import {
requestApproval,
requiresApprovalForRepo,
isQualityMonitoringRepo,
sendQualityMonitoringNotification,
} from "../github/optimization_approval.js"
import { registerRepositoryAndMember } from "./utils/github-repo-setup.js"
import { saveStagingReview } from "./create-staging.js"
import { AnyOctokit, AuthorizedUserReq, PullRequestDB } from "../types.js"
import { OptimizationReview } from "../OptimizationReview.js"
import {
missingRequiredFields,
validationFailure,
unauthorized,
githubInstallationError,
githubNotCollaborator,
optimizationRejected,
unprocessableEntity,
internalServerError,
} from "../exceptions/index.js"
// Dependencies interface for easier testing
export interface SuggestPrChangesDependencies {
prisma: PrismaClient
userNickname: typeof userNickname
getInstallationOctokitByOwner: typeof getInstallationOctokitByOwner
isUserCollaborator: typeof isUserCollaborator
requiresApproval: typeof requiresApprovalForRepo
requestApproval: typeof requestApproval
posthog: typeof posthog
githubApp: typeof githubApp
isDiffContentsWellFormed: typeof isDiffContentsWellFormed
fileDiffsToMap: typeof fileDiffsToMap
determineValidHunks: typeof determineValidHunks
buildDependentPrTitle: typeof buildDependentPrTitle
buildPrCommentBody: typeof buildPrCommentBody
createNewBranchFromDiffContents: typeof createNewBranchFromDiffContents
createDependentPullRequest: typeof createDependentPullRequest
sendSlackMessage: typeof sendSlackMessage
updateOptimizationEvent: typeof updateOptimizationEvent
saveStagingReview: typeof saveStagingReview
}
// Default dependencies
let dependencies: SuggestPrChangesDependencies = {
prisma,
userNickname,
getInstallationOctokitByOwner,
isUserCollaborator,
requiresApproval: requiresApprovalForRepo,
requestApproval,
posthog,
githubApp,
isDiffContentsWellFormed,
fileDiffsToMap,
determineValidHunks,
buildDependentPrTitle,
buildPrCommentBody,
createNewBranchFromDiffContents,
createDependentPullRequest,
sendSlackMessage,
updateOptimizationEvent,
saveStagingReview,
}
// For testing - allow dependency injection
export function setSuggestPrChangesDependencies(deps: Partial<SuggestPrChangesDependencies>) {
dependencies = { ...dependencies, ...deps }
}
export function resetSuggestPrChangesDependencies() {
dependencies = {
prisma,
userNickname,
getInstallationOctokitByOwner,
isUserCollaborator,
requiresApproval: requiresApprovalForRepo,
requestApproval,
posthog,
githubApp,
isDiffContentsWellFormed,
fileDiffsToMap,
determineValidHunks,
buildDependentPrTitle,
buildPrCommentBody,
createNewBranchFromDiffContents,
createDependentPullRequest,
sendSlackMessage,
updateOptimizationEvent,
saveStagingReview,
}
}
const slackNotificationConfig = {
"Future-House": ["aviary", "paper-qa"],
"langflow-ai": ["langflow"],
"albumentations-team": ["albumentations"],
"Skyvern-AI": ["skyvern"],
roboflow: ["inference"],
gdsfactory: ["gdsfactory"],
stackai: ["stackend"],
}
// Helper function to parse speedup values (same as create-staging.ts)
function parseSpeedupValue(value: unknown, suffix: "x" | "%"): number | null {
if (value === undefined || value === null || value === "") {
return null
}
let stringValue = String(value).trim().toLowerCase()
if (stringValue.endsWith(suffix)) {
stringValue = stringValue.slice(0, -1)
}
const parsed = parseFloat(stringValue.replace(",", ""))
return isNaN(parsed) ? null : parsed
}
// Utility function to update optimization event in the database
export async function updateOptimizationEvent(
traceId: string,
prId?: string,
prCommentFields?: any,
originalLineProfiler?: string,
optimizedLineProfiler?: string,
) {
if (traceId !== "") {
try {
// Check existing data first (preserve staging data)
const existing = await dependencies.prisma.optimization_events.findUnique({
where: { trace_id: traceId },
select: {
function_name: true,
speedup_x: true,
file_path: true,
speedup_pct: true,
metadata: true,
},
})
// Extract data from prCommentFields if provided
const updateData: any = {
...(prId ? { pr_id: String(prId) } : {}),
is_optimization_found: true,
event_type: prId ? "pr_created" : "no-pr",
}
// Only add optimization data if prCommentFields provided AND not already set
if (prCommentFields) {
if (!existing?.function_name && prCommentFields.function_name) {
updateData.function_name = prCommentFields.function_name
}
if (!existing?.file_path && prCommentFields.file_path) {
updateData.file_path = prCommentFields.file_path
}
if (existing?.speedup_x == null && prCommentFields.speedup_x) {
updateData.speedup_x = parseSpeedupValue(prCommentFields.speedup_x, "x")
}
if (existing?.speedup_pct == null && prCommentFields.speedup_pct) {
updateData.speedup_pct = parseSpeedupValue(prCommentFields.speedup_pct, "%")
}
}
// Add line profiler data to metadata if provided
if (originalLineProfiler || optimizedLineProfiler) {
const currentMetadata = (existing?.metadata ?? {}) as Record<string, unknown>
if (originalLineProfiler && !currentMetadata.originalLineProfiler) {
currentMetadata.originalLineProfiler = originalLineProfiler
}
if (optimizedLineProfiler && !currentMetadata.optimizedLineProfiler) {
currentMetadata.optimizedLineProfiler = optimizedLineProfiler
}
updateData.metadata = currentMetadata
}
await dependencies.prisma.optimization_events.update({
where: { trace_id: traceId },
data: updateData,
})
} catch (eventError) {
logger.error(
"Failed to update optimization event:",
{ endpoint: "/cfapi/suggest-pr-changes", operation: "update_optimization_event" },
{},
eventError as Error,
)
}
}
}
export async function suggestPrChanges(
req: AuthorizedUserReq,
res: Response,
): Promise<Response | undefined> {
try {
const {
owner,
repo,
pullNumber,
diffContents,
prCommentFields,
existingTests,
generatedTests,
coverage_message,
replayTests,
concolicTests,
optimizationReview,
originalLineProfiler,
optimizedLineProfiler,
} = req.body
const userId = req.userId
const traceId = req.body.traceId || ""
logger.info(`traceId: ${traceId}`, req)
if (!repo || !owner || !pullNumber || !dependencies.isDiffContentsWellFormed(diffContents)) {
throw validationFailure("Missing or malformed fields: repo, owner, pullNumber, diffContents")
}
const nickname = await dependencies.userNickname(userId)
if (nickname == null) {
throw unauthorized("")
}
const installationOctokit = await dependencies.getInstallationOctokitByOwner(
dependencies.githubApp,
owner,
repo,
userId,
)
if (installationOctokit instanceof Error) {
throw githubInstallationError(installationOctokit.message)
}
const isCollaborator = await dependencies.isUserCollaborator(
installationOctokit,
owner,
repo,
nickname,
)
if (!isCollaborator) {
logger.info(`${nickname} is not a collaborator on ${owner}/${repo}`, req)
throw githubNotCollaborator(`${owner}/${repo}`)
}
logger.info(`${nickname} is a collaborator on ${owner}/${repo}`, req)
// TODO: Remove this background upsert logic after ensuring all old repositories have been saved.
registerRepositoryAndMember(owner, repo, nickname, userId, installationOctokit)
.then(() => {
logger.info(`Background repo and member upsert completed for ${owner}/${repo}`, req)
})
.catch(err => {
logger.errorWithSentry(
`Error in background upsertRepoAndCreateMember`,
req,
{ owner, repo, nickname, userId },
err as Error,
)
})
// Check if approval is required
if (traceId && dependencies.requiresApproval(owner, repo)) {
const optimization = await dependencies.prisma.optimization_features.findUnique({
where: { trace_id: traceId },
select: {
approval_required: true,
approval_status: true,
},
})
if (optimization?.approval_status === "rejected") {
throw optimizationRejected("This optimization request was rejected")
}
if (optimization?.approval_status === "approved") {
logger.info(
`Request ${traceId} was previously approved, continuing with PR suggestion`,
req,
)
const result = await triggerSuggestPrChanges(
owner,
repo,
pullNumber,
diffContents,
prCommentFields,
existingTests,
generatedTests,
coverage_message,
userId,
nickname,
installationOctokit,
replayTests,
concolicTests,
traceId,
optimizationReview,
res,
originalLineProfiler,
optimizedLineProfiler,
)
if (result && typeof result === "object" && "status" in result) {
return result
}
return res.json(result)
} else {
const requestData = {
type: "suggest-pr-changes",
owner,
repo,
pullNumber,
diffContents,
prCommentFields,
existingTests,
generatedTests,
coverage_message,
userId,
optimizationReview,
originalLineProfiler,
optimizedLineProfiler,
}
await dependencies.requestApproval(
traceId,
owner,
repo,
prCommentFields.function_name,
userId,
requestData,
)
return res.status(202).json({
status: "pending_approval",
message:
"This PR suggestion requires approval. You will be notified when it is processed.",
})
}
}
// No approval required, proceed with PR suggestion
const result = await triggerSuggestPrChanges(
owner,
repo,
pullNumber,
diffContents,
prCommentFields,
existingTests,
generatedTests,
coverage_message,
userId,
nickname,
installationOctokit,
replayTests,
concolicTests,
traceId,
optimizationReview,
res,
originalLineProfiler,
optimizedLineProfiler,
)
// Check if this is a quality monitoring repo and send notification
if (traceId && isQualityMonitoringRepo(owner, repo) && result && typeof result === "number") {
const requestData = {
type: "suggest-pr-changes",
owner,
repo,
pullNumber,
diffContents,
prCommentFields,
existingTests,
generatedTests,
coverage_message,
userId,
replayTests,
concolicTests,
optimizationReview,
originalLineProfiler,
optimizedLineProfiler,
}
// Send quality monitoring notification (non-blocking)
sendQualityMonitoringNotification(
traceId,
owner,
repo,
prCommentFields.function_name,
userId,
requestData,
).catch(error => {
logger.error(`Failed to send quality monitoring notification:`, req, {}, error as Error)
})
logger.info(
`Quality monitoring notification triggered for ${owner}/${repo} PR #${pullNumber}`,
req,
)
}
// Don't call res.json(result) if result is already a Response object
if (result && typeof result === "object" && "status" in result) {
return result
}
return res.json(result)
} catch (error: any) {
// Try to fallback to staging if we have a traceId
const traceId = req.body.traceId || ""
if (traceId) {
logger.info(`PR suggestion failed, falling back to staging for traceId: ${traceId}`, req)
try {
const stagingResult = await dependencies.saveStagingReview(
req.body,
req.userId,
req.organizationId,
req.subscriptionInfo,
)
if (stagingResult.status === 200) {
return res.status(200).json({
message: "PR suggestion failed, staging created as fallback",
...stagingResult.data,
})
}
// Handle non-200 staging response - return staging's actual status/error
logger.errorWithSentry(
`Staging fallback returned status ${stagingResult.status}`,
req,
{ reqBody: req.body, userId: req.userId, traceId, stagingResult },
new Error(`Staging fallback returned status ${stagingResult.status}`),
)
return res.status(stagingResult.status).json({
message: "PR suggestion failed and staging fallback also failed",
...stagingResult.data,
})
} catch (stagingError) {
logger.errorWithSentry(
`Staging fallback threw an exception`,
req,
{ reqBody: req.body, userId: req.userId, traceId },
stagingError as Error,
)
return res.status(500).json({
message: "PR suggestion failed and staging fallback threw an error",
error: stagingError instanceof Error ? stagingError.message : String(stagingError),
})
}
}
logger.errorWithSentry(
`Error in /cfapi/suggest-pr-changes: ${error}`,
req,
{ errorMessage: error.message },
error as Error,
)
dependencies.posthog.capture({
distinctId: req.userId,
event: `cfapi-suggest-pr-changes-failed-error`,
properties: { error: error.message, stack: error.stack },
})
throw internalServerError(`Error creating pull request: ${error.message}`)
}
}
export async function triggerSuggestPrChanges(
owner: string,
repo: string,
pullNumber: number,
diffContents: any,
prCommentFields: any,
existingTests: string,
generatedTests: string,
coverage_message: string,
userId: string,
nickname: string,
installationOctokit: any,
replayTests: string = "",
concolicTests: string = "",
traceId: string = "",
optimizationReview: string = "",
res?: Response,
originalLineProfiler: string = "",
optimizedLineProfiler: string = "",
): Promise<Response | number | null> {
try {
const diffContentsMap: Map<string, FileDiffContent> = dependencies.fileDiffsToMap(diffContents)
const { validHunks, invalidHunks } = await dependencies.determineValidHunks(
installationOctokit.rest,
{ owner, repo },
pullNumber,
100,
diffContentsMap,
)
const timestamp = new Date()
.toISOString()
.replace(/:/g, ".")
.replace(/\.\d+Z$/, "")
const newBranchName = `codeflash/optimize-pr${pullNumber}-${timestamp}`
const originalPrData = await installationOctokit.rest.pulls.get({
owner,
repo,
pull_number: pullNumber,
})
// Check if the PR is merged or closed - we can't suggest changes on merged/closed PRs
if (originalPrData.data.merged) {
logger.info(`PR #${pullNumber} is already merged, cannot suggest changes`, {
endpoint: "/cfapi/suggest-pr-changes",
operation: "pr_merged_check",
owner,
repo,
userId,
})
throw unprocessableEntity(
`Cannot suggest changes on merged PR #${pullNumber}. The PR was already merged.`,
)
}
if (originalPrData.data.state === "closed") {
logger.info(`PR #${pullNumber} is closed, cannot suggest changes`, {
endpoint: "/cfapi/suggest-pr-changes",
operation: "pr_closed_check",
owner,
repo,
userId,
})
throw unprocessableEntity(
`Cannot suggest changes on closed PR #${pullNumber}. The PR is no longer open.`,
)
}
const baseBranch = originalPrData.data.head.ref
logger.info(`Attempting to access ref for: ${owner}/${repo}, branch: ${baseBranch}`, {
endpoint: "/cfapi/suggest-pr-changes",
operation: "access_ref",
owner,
repo,
userId,
})
const commitMessage = `Optimize ${prCommentFields.function_name} \n\n${prCommentFields.optimization_explanation}`
let hasMultipleHunksInSameFile = false
const hasMultipleFiles = validHunks.size > 1
for (const [filePath, hunks] of validHunks.entries()) {
if (hunks.length > 1) {
logger.info(
`File ${filePath} has ${hunks.length} hunks, using dependent PR instead of review comments`,
{
endpoint: "/cfapi/suggest-pr-changes",
operation: "multiple_hunks",
owner,
repo,
userId,
},
)
hasMultipleHunksInSameFile = true
break
}
}
if (hasMultipleFiles) {
logger.info(
`Found ${validHunks.size} files with changes, using dependent PR instead of review comments`,
{ endpoint: "/cfapi/suggest-pr-changes", operation: "multiple_files", owner, repo, userId },
)
}
if (
invalidHunks.size > 0 ||
validHunks.size > 1 ||
hasMultipleFiles ||
hasMultipleHunksInSameFile
) {
logger.info(
`Creating a dependent PR because there are ${invalidHunks.size > 0 ? "invalid hunks" : "multiple valid hunks"}.`,
{
userId,
endpoint: "/cfapi/suggest-pr-changes",
operation: "create_dependent_pr",
owner,
repo,
},
)
logger.info(`Making a new dependent PR...`, {
userId,
endpoint: "/cfapi/suggest-pr-changes",
operation: "create_dependent_pr",
owner,
repo,
})
if (
OptimizationReview.MEDIUM === optimizationReview &&
!dependencies.requiresApproval(owner, repo) &&
traceId
) {
const result = await dependencies.saveStagingReview(
{
owner,
repo,
pullNumber,
baseBranch,
prCommentFields,
existingTests,
generatedTests,
coverage_message,
replayTests,
concolicTests,
optimizationReview,
traceId,
diffContents,
},
userId,
null, // organizationId - not available in this context
)
if (result.status == 200) {
const commentBody: string = originalPRComment(
prCommentFields,
traceId, //We will send trace Id instead of PRNumber
baseBranch,
optimizationReview,
)
await installationOctokit.rest.issues.createComment({
owner,
repo,
issue_number: pullNumber,
body: commentBody,
})
if (res) {
return res.status(200).json({ message: "staging created successfully" })
}
}
}
const branchCreated = await dependencies.createNewBranchFromDiffContents(
installationOctokit,
owner,
repo,
newBranchName,
baseBranch,
diffContentsMap,
commitMessage,
)
if (!branchCreated) {
throw new Error(`Failed to create branch ${newBranchName}`)
}
const newPrData = await dependencies.createDependentPullRequest(
installationOctokit,
owner,
repo,
pullNumber,
newBranchName,
baseBranch,
prCommentFields,
existingTests,
generatedTests,
coverage_message,
replayTests,
concolicTests,
optimizationReview,
)
logger.info(
`Created new dependent PR #${newPrData.data.number} from branch ${newPrData.data.head.ref}`,
{
userId,
endpoint: "/cfapi/suggest-pr-changes",
operation: "dependent_pr_created",
owner,
repo,
},
)
if (slackNotificationConfig[owner as keyof typeof slackNotificationConfig]?.includes(repo)) {
await dependencies.sendSlackMessage(
`new dependent PR created: ${newPrData.data.html_url} for ${owner}/${repo}`,
)
}
dependencies.posthog?.capture({
distinctId: userId,
event: `cfapi-suggest-pr-changes-success-dependent-pr-created`,
properties: {
owner,
repo,
newPrNumber: newPrData.data.number,
newPrBranch: newPrData.data.head.ref,
PRURL: newPrData.data.html_url,
},
})
if (traceId !== "") {
const pull_request_db = await dependencies.prisma.optimization_features.findUnique({
where: {
trace_id: traceId,
},
select: {
pull_request: true,
},
})
if (pull_request_db) {
if (pull_request_db.pull_request === null || pull_request_db.pull_request === undefined) {
pull_request_db.pull_request = {}
}
;(pull_request_db as PullRequestDB).pull_request.dependent_pr_url =
newPrData.data.html_url
await dependencies.prisma.optimization_features.update({
where: {
trace_id: traceId,
},
data: {
pull_request: pull_request_db.pull_request,
},
})
}
}
await dependencies.updateOptimizationEvent(
traceId,
newPrData.data.id,
prCommentFields,
originalLineProfiler,
optimizedLineProfiler,
)
if (res) {
res.json(newPrData.data.number)
return res
}
return newPrData.data.number
} else {
logger.info(`Creating unified review for a single valid hunk.`, {
userId,
endpoint: "/cfapi/suggest-pr-changes",
operation: "create_unified_review",
owner,
repo,
})
const prCommentBody = dependencies.buildPrCommentBody(
prCommentFields,
existingTests,
generatedTests,
coverage_message,
newBranchName,
replayTests,
concolicTests,
traceId,
{ isUnifiedReview: true, includeHeader: false, isCollapsed: true },
)
let optReviewBadge = generateOptimizationReviewTemplate(optimizationReview)
optReviewBadge &&= `\n\n${optReviewBadge}\n`
const reviewComments = []
let foundInvalidHunk = false
for (const [filePath, hunks] of validHunks.entries()) {
for (const hunk of hunks) {
if (hunk.oldStart <= hunk.oldEnd) {
const newContent = hunk.newContent.join("\n")
const isLongDiff = newContent.length > 500
let commentBody
if (isLongDiff) {
commentBody =
`${prCommentBody}\n\n` +
`<details>\n` +
`<summary>Click to see suggested changes</summary>\n\n` +
`\`\`\`suggestion\n${newContent}\n\`\`\`\n` +
`</details>` +
`\n${optReviewBadge}`
} else {
commentBody =
`${prCommentBody}\n\n` +
`\`\`\`suggestion\n${newContent}\n\`\`\`` +
`\n${optReviewBadge}`
}
reviewComments.push({
path: filePath,
line: hunk.oldEnd,
start_line: hunk.oldStart,
side: "RIGHT",
body: commentBody,
})
} else {
logger.warn(
`Found invalid review range for ${filePath}: start_line (${hunk.oldStart}) must be less than or equal to line (${hunk.oldEnd}) with content ${hunk.newContent.join("\n")}`,
{
userId,
endpoint: "/cfapi/suggest-pr-changes",
operation: "invalid_review_range",
owner,
repo,
},
)
foundInvalidHunk = true
break
}
}
if (foundInvalidHunk) {
break
}
}
if (!foundInvalidHunk && reviewComments.length > 0) {
const review = await installationOctokit.rest.pulls.createReview({
owner,
repo,
pull_number: pullNumber,
commit_id: originalPrData.data.head.sha,
event: "COMMENT",
comments: reviewComments,
})
logger.info(`Added review comment to PR #${pullNumber}: ${review.data.html_url}`, {
userId,
endpoint: "/cfapi/suggest-pr-changes",
operation: "review_comment_added",
owner,
repo,
})
if (
slackNotificationConfig[owner as keyof typeof slackNotificationConfig]?.includes(repo)
) {
await dependencies.sendSlackMessage(
`Suggestions made for ${review.data.html_url} in ${owner}/${repo}`,
)
}
dependencies.posthog?.capture({
distinctId: userId,
event: `cfapi-suggest-pr-changes-success-suggestions-made`,
properties: {
owner,
repo,
reviewId: review.data.id,
PRURL: review.data.html_url,
},
})
if (traceId !== "") {
const pull_request_db = await dependencies.prisma.optimization_features.findUnique({
where: {
trace_id: traceId,
},
select: {
pull_request: true,
},
})
if (pull_request_db) {
if (
pull_request_db.pull_request === null ||
pull_request_db.pull_request === undefined
) {
pull_request_db.pull_request = {}
}
;(pull_request_db as PullRequestDB).pull_request.review_suggestion_pr_url =
review.data.html_url
await dependencies.prisma.optimization_features.update({
where: {
trace_id: traceId,
},
data: {
pull_request: pull_request_db.pull_request,
},
})
}
}
await dependencies.updateOptimizationEvent(
traceId,
undefined,
prCommentFields,
originalLineProfiler,
optimizedLineProfiler,
)
if (res) {
res.json(review.data.id)
return res
}
return review.data.id
} else {
const reason = foundInvalidHunk
? "invalid line ordering in hunks"
: "no valid review comments could be created"
logger.warn(`Cannot create review due to ${reason}`, {
userId,
endpoint: "/cfapi/suggest-pr-changes",
operation: "cannot_create_review",
owner,
repo,
})
logger.error(`Cannot create review comments due to ${reason}`, {
userId,
endpoint: "/cfapi/suggest-pr-changes",
operation: "cannot_create_review",
owner,
repo,
})
throw unprocessableEntity(
`Cannot create review comments due to ${reason}. Please consider creating a dependent PR instead`,
)
}
}
} catch (error: any) {
// Re-throw AppExceptions to be handled by GlobalExceptionHandler
if (error && typeof error === "object" && "getHttpStatus" in error) {
throw error
}
logger.errorWithSentry(
`Error in triggerSuggestPrChanges: ${error}`,
{
userId,
endpoint: "/cfapi/suggest-pr-changes",
operation: "trigger_suggest_pr_changes",
owner,
repo,
},
{
errorMessage: error.message,
},
error as Error,
)
throw internalServerError(`Error creating pull request: ${error.message}`)
}
}