Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/web/.env.development.local.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# @url cloud-agent-next
CLOUD_AGENT_NEXT_API_URL=http://localhost:8794

# @from CALLBACK_TOKEN_SECRET
CALLBACK_TOKEN_SECRET=

# @override
CLOUD_AGENT_R2_ATTACHMENTS_BUCKET_NAME=cloud-agent-attachments-dev

Expand Down
30 changes: 22 additions & 8 deletions apps/web/src/app/(app)/code-reviews/ReviewAgentPageClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ type ReviewAgentPageClientProps = {
successMessage?: string;
errorMessage?: string;
initialPlatform?: Platform;
localCodeReviewDevelopmentEnabled?: boolean;
};

export function ReviewAgentPageClient({
successMessage,
errorMessage,
initialPlatform = 'github',
localCodeReviewDevelopmentEnabled = false,
}: ReviewAgentPageClientProps) {
const trpc = useTRPC();
const router = useRouter();
Expand Down Expand Up @@ -66,6 +68,8 @@ export function ReviewAgentPageClient({
const isGitHubAppInstalled =
githubStatusData?.connected && githubStatusData?.integration?.isValid;
const isGitLabConnected = gitlabStatusData?.connected && gitlabStatusData?.integration?.isValid;
const canUseGitHubJobs = isGitHubAppInstalled || localCodeReviewDevelopmentEnabled;
const canUseGitLabJobs = isGitLabConnected || localCodeReviewDevelopmentEnabled;

// Show toast messages from URL params
useEffect(() => {
Expand Down Expand Up @@ -139,7 +143,7 @@ export function ReviewAgentPageClient({
{/* GitHub Tab Content */}
<TabsContent value="github" className="mt-6 space-y-6">
{/* GitHub App Required Alert */}
{!isGitHubAppInstalled && (
{!isGitHubAppInstalled && !localCodeReviewDevelopmentEnabled && (
<Alert>
<Rocket className="h-4 w-4" />
<AlertTitle>GitHub App Required</AlertTitle>
Expand Down Expand Up @@ -172,7 +176,7 @@ export function ReviewAgentPageClient({
<TabsTrigger
value="jobs"
className="flex items-center gap-2"
disabled={!isGitHubAppInstalled}
disabled={!canUseGitHubJobs}
>
<ListChecks className="h-4 w-4" />
Jobs
Expand All @@ -192,8 +196,13 @@ export function ReviewAgentPageClient({
</TabsContent>

<TabsContent value="jobs" className="mt-6 space-y-4">
{isGitHubAppInstalled ? (
<CodeReviewJobsCard platform="github" />
{canUseGitHubJobs ? (
<CodeReviewJobsCard
platform="github"
localCodeReviewDevelopmentEnabled={localCodeReviewDevelopmentEnabled}
defaultModelSlug={selectedConfigData?.modelSlug}
defaultThinkingEffort={selectedConfigData?.thinkingEffort}
/>
) : (
<Alert>
<ListChecks className="h-4 w-4" />
Expand Down Expand Up @@ -225,7 +234,7 @@ export function ReviewAgentPageClient({
{/* GitLab Tab Content */}
<TabsContent value="gitlab" className="mt-6 space-y-6">
{/* GitLab Connection Required Alert */}
{!isGitLabConnected && (
{!isGitLabConnected && !localCodeReviewDevelopmentEnabled && (
<Alert>
<Rocket className="h-4 w-4" />
<AlertTitle>GitLab Connection Required</AlertTitle>
Expand Down Expand Up @@ -258,7 +267,7 @@ export function ReviewAgentPageClient({
<TabsTrigger
value="jobs"
className="flex items-center gap-2"
disabled={!isGitLabConnected}
disabled={!canUseGitLabJobs}
>
<ListChecks className="h-4 w-4" />
Jobs
Expand Down Expand Up @@ -286,8 +295,13 @@ export function ReviewAgentPageClient({
</TabsContent>

<TabsContent value="jobs" className="mt-6 space-y-4">
{isGitLabConnected ? (
<CodeReviewJobsCard platform="gitlab" />
{canUseGitLabJobs ? (
<CodeReviewJobsCard
platform="gitlab"
localCodeReviewDevelopmentEnabled={localCodeReviewDevelopmentEnabled}
defaultModelSlug={selectedConfigData?.modelSlug}
defaultThinkingEffort={selectedConfigData?.thinkingEffort}
/>
) : (
<Alert>
<ListChecks className="h-4 w-4" />
Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/app/(app)/code-reviews/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getUserFromAuthOrRedirect } from '@/lib/user/server';
import { isLocalCodeReviewDevelopmentEnabled } from '@/lib/config.server';
import { ReviewAgentPageClient } from './ReviewAgentPageClient';

type ReviewAgentPageProps = {
Expand All @@ -9,6 +10,7 @@ export default async function PersonalReviewAgentPage({ searchParams }: ReviewAg
const search = await searchParams;
const user = await getUserFromAuthOrRedirect('/users/sign_in?callbackPath=/code-reviews');
const platform = search.platform === 'gitlab' ? 'gitlab' : 'github';
const localCodeReviewDevelopmentEnabled = isLocalCodeReviewDevelopmentEnabled();

return (
<ReviewAgentPageClient
Expand All @@ -17,6 +19,7 @@ export default async function PersonalReviewAgentPage({ searchParams }: ReviewAg
successMessage={search.success}
errorMessage={search.error}
initialPlatform={platform}
localCodeReviewDevelopmentEnabled={localCodeReviewDevelopmentEnabled}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type ReviewAgentPageClientProps = {
successMessage?: string;
errorMessage?: string;
initialPlatform?: Platform;
localCodeReviewDevelopmentEnabled?: boolean;
returnTo?: string;
};

Expand All @@ -45,6 +46,7 @@ export function ReviewAgentPageClient({
successMessage,
errorMessage,
initialPlatform = 'github',
localCodeReviewDevelopmentEnabled = false,
returnTo,
}: ReviewAgentPageClientProps) {
const trpc = useTRPC();
Expand Down Expand Up @@ -90,6 +92,8 @@ export function ReviewAgentPageClient({
const isGitHubAppInstalled =
githubStatusData?.connected && githubStatusData?.integration?.isValid;
const isGitLabConnected = gitlabStatusData?.connected && gitlabStatusData?.integration?.isValid;
const canUseGitHubJobs = isGitHubAppInstalled || localCodeReviewDevelopmentEnabled;
const canUseGitLabJobs = isGitLabConnected || localCodeReviewDevelopmentEnabled;
const returnPath = returnTo
? `${returnTo}${returnTo.includes('?') ? '&' : '?'}code_reviewer_return=true`
: null;
Expand Down Expand Up @@ -179,7 +183,7 @@ export function ReviewAgentPageClient({
{/* GitHub Tab Content */}
<TabsContent value="github" className="mt-6 space-y-6">
{/* GitHub App Required Alert */}
{!isGitHubAppInstalled && (
{!isGitHubAppInstalled && !localCodeReviewDevelopmentEnabled && (
<Alert>
<Rocket className="h-4 w-4" />
<AlertTitle>GitHub App Required</AlertTitle>
Expand Down Expand Up @@ -215,7 +219,7 @@ export function ReviewAgentPageClient({
<TabsTrigger
value="jobs"
className="flex items-center gap-2"
disabled={!isGitHubAppInstalled}
disabled={!canUseGitHubJobs}
>
<ListChecks className="h-4 w-4" />
Jobs
Expand All @@ -239,8 +243,14 @@ export function ReviewAgentPageClient({
</TabsContent>

<TabsContent value="jobs" className="mt-6 space-y-4">
{isGitHubAppInstalled ? (
<CodeReviewJobsCard organizationId={organizationId} platform="github" />
{canUseGitHubJobs ? (
<CodeReviewJobsCard
organizationId={organizationId}
platform="github"
localCodeReviewDevelopmentEnabled={localCodeReviewDevelopmentEnabled}
defaultModelSlug={selectedConfigData?.modelSlug}
defaultThinkingEffort={selectedConfigData?.thinkingEffort}
/>
) : (
<Alert>
<ListChecks className="h-4 w-4" />
Expand Down Expand Up @@ -276,7 +286,7 @@ export function ReviewAgentPageClient({
{/* GitLab Tab Content */}
<TabsContent value="gitlab" className="mt-6 space-y-6">
{/* GitLab Connection Required Alert */}
{!isGitLabConnected && (
{!isGitLabConnected && !localCodeReviewDevelopmentEnabled && (
<Alert>
<Rocket className="h-4 w-4" />
<AlertTitle>GitLab Connection Required</AlertTitle>
Expand Down Expand Up @@ -312,7 +322,7 @@ export function ReviewAgentPageClient({
<TabsTrigger
value="jobs"
className="flex items-center gap-2"
disabled={!isGitLabConnected}
disabled={!canUseGitLabJobs}
>
<ListChecks className="h-4 w-4" />
Jobs
Expand Down Expand Up @@ -345,8 +355,14 @@ export function ReviewAgentPageClient({
</TabsContent>

<TabsContent value="jobs" className="mt-6 space-y-4">
{isGitLabConnected ? (
<CodeReviewJobsCard organizationId={organizationId} platform="gitlab" />
{canUseGitLabJobs ? (
<CodeReviewJobsCard
organizationId={organizationId}
platform="gitlab"
localCodeReviewDevelopmentEnabled={localCodeReviewDevelopmentEnabled}
defaultModelSlug={selectedConfigData?.modelSlug}
defaultThinkingEffort={selectedConfigData?.thinkingEffort}
/>
) : (
<Alert>
<ListChecks className="h-4 w-4" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { OrganizationByPageLayout } from '@/components/organizations/OrganizationByPageLayout';
import { isLocalCodeReviewDevelopmentEnabled } from '@/lib/config.server';
import { ReviewAgentPageClient } from './ReviewAgentPageClient';
import { validateReturnPath } from '@/lib/integrations/validate-return-path';

Expand All @@ -15,6 +16,7 @@ type ReviewAgentPageProps = {
export default async function ReviewAgentPage({ params, searchParams }: ReviewAgentPageProps) {
const search = await searchParams;
const platform = search.platform === 'gitlab' ? 'gitlab' : 'github';
const localCodeReviewDevelopmentEnabled = isLocalCodeReviewDevelopmentEnabled();
const returnTo = search.returnTo ? validateReturnPath(search.returnTo) : null;

return (
Expand All @@ -27,6 +29,7 @@ export default async function ReviewAgentPage({ params, searchParams }: ReviewAg
successMessage={search.success}
errorMessage={search.error}
initialPlatform={platform}
localCodeReviewDevelopmentEnabled={localCodeReviewDevelopmentEnabled}
returnTo={returnTo ?? undefined}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ function makeReview(overrides: Partial<CloudAgentCodeReview> = {}): CloudAgentCo
repository_review_instructions_truncated: false,
previous_summary_body: null,
previous_summary_head_sha: null,
manual_config: null,
model: null,
total_tokens_in: null,
total_tokens_out: null,
Expand Down Expand Up @@ -1367,6 +1368,39 @@ describe('POST /api/internal/code-review-status/[reviewId]', () => {
expect(mockTryDispatchPendingReviews).not.toHaveBeenCalled();
});

it('does not retry assistant authorization failures as infra failures', async () => {
const retryFlow = mockCreatedInfraRetryFlow({
failedAttemptId: '00000000-0000-0000-0000-000000000207',
retryAttemptId: '00000000-0000-0000-0000-000000000208',
sessionId: 'agent-auth-failed-old',
});
mockGetCodeReviewById.mockResolvedValue(
makeReview({ status: 'running', session_id: retryFlow.sessionId })
);

const response = await POST(
makeRequest({
status: 'failed',
cloudAgentSessionId: retryFlow.sessionId,
errorMessage: 'Assistant request was not authorized',
terminalReason: 'upstream_error',
}),
makeParams(REVIEW_ID)
);

expect(response.status).toBe(200);
expect(mockCreateInfraRetryAttemptIfMissing).not.toHaveBeenCalled();
expect(mockRetryReviewFresh).not.toHaveBeenCalled();
expect(mockUpdateCodeReviewStatus).toHaveBeenCalledWith(
REVIEW_ID,
'failed',
expect.objectContaining({
errorMessage: 'Assistant request was not authorized',
terminalReason: 'upstream_error',
})
);
});

it.each([
'prepareSession failed (400): {"error":{"message":"[\n {\n "origin": "string",\n "code": "invalid_format",\n "format": "regex"\n }\n]","code":-32600,"data":{"code":"BAD_REQUEST","httpStatus":400,"path":"prepareSession"}}}',
'prepareSession failed (500): {"error":{"message":"Unexpected prepareSession server failure","code":-32603,"data":{"code":"INTERNAL_SERVER_ERROR","httpStatus":500,"path":"prepareSession"}}}',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ import {
import type { Owner } from '@/lib/code-reviews/core';
import { parseCodeReviewAnalyticsManifest } from '@/lib/code-reviews/analytics/contracts';
import { finalizeCompletedCodeReviewWithAnalytics } from '@/lib/code-reviews/analytics/db';
import {
getManualCodeReviewConfig,
shouldPublishCodeReviewToProvider,
} from '@/lib/code-reviews/manual-config';

const CallbackTextTruncationSchema = z
.object({
Expand Down Expand Up @@ -407,6 +411,8 @@ function hasKnownUnretryableFailureMessage(errorMessage?: string | null): boolea

return (
message.includes('maximum runtime') ||
message.includes('assistant request was not authorized') ||
/\b(unauthorized|authentication|authorization|forbidden|401|403)\b/i.test(message) ||
/\b(cancelled|canceled)\b/i.test(message) ||
message.includes('superseded') ||
message.includes('user interrupted') ||
Expand Down Expand Up @@ -1012,6 +1018,10 @@ export async function POST(
return NextResponse.json({ error: 'Review not found' }, { status: 404 });
}

const manualConfig = getManualCodeReviewConfig(review);
const isManualReview = manualConfig !== null;
const shouldPublishToProvider = shouldPublishCodeReviewToProvider(review);

const callbackCompletedAt = new Date();
let attempt: CloudAgentCodeReviewAttempt;
let latestAttempt = await getLatestCodeReviewAttempt(reviewId);
Expand Down Expand Up @@ -1332,7 +1342,7 @@ export async function POST(
let providerTerminalReason = terminalReason;
const actionRequiredReason =
status === 'failed' ? getActionRequiredTerminalReason(terminalReason, errorMessage) : null;
if (actionRequiredReason) {
if (actionRequiredReason && !isManualReview) {
const ownerResolution = await getTerminalOwnerResolution();
if (ownerResolution) {
try {
Expand All @@ -1354,7 +1364,7 @@ export async function POST(
});
}
}
} else if (status === 'failed') {
} else if (status === 'failed' && !isManualReview) {
const ownerResolution = await getTerminalOwnerResolution();
if (ownerResolution) {
try {
Expand All @@ -1381,9 +1391,10 @@ export async function POST(
}

// Fetch integration once — used for gate check updates and post-completion actions
const integration = review.platform_integration_id
? await getIntegrationById(review.platform_integration_id)
: null;
const integration =
shouldPublishToProvider && review.platform_integration_id
? await getIntegrationById(review.platform_integration_id)
: null;

// Resolve GitLab token once, shared between gate check and reaction/footer logic
const isGitLab = (review.platform || 'github') === PLATFORM.GITLAB;
Expand Down
Loading