diff --git a/ENVIRONMENT.md b/ENVIRONMENT.md index ad473d6719..d38935b390 100644 --- a/ENVIRONMENT.md +++ b/ENVIRONMENT.md @@ -36,7 +36,7 @@ This document lists all environment variables used in the Kilo Code cloud monore - `SENTRY_ORG` - Sentry organization slug for source map uploads; used in `apps/web/next.config.mjs`. `[SECRET]` - `SENTRY_PROJECT` - Sentry project slug for source map uploads; used in `apps/web/next.config.mjs`. `[SECRET]` - `SENTRY_AUTH_TOKEN` - Sentry auth token for source map uploads; used in `apps/web/next.config.mjs`. `[SECRET]` -- `NEXT_PUBLIC_SENTRY_DSN` - Sentry DSN for client-side error reporting; used in `apps/web/instrumentation-client.ts`, `sentry.edge.config.ts`, `sentry.server.config.ts`. `[PUBLIC]` +- `NEXT_PUBLIC_SENTRY_DSN` - Sentry DSN for server and Edge runtime error reporting; used in `apps/web/sentry.edge.config.ts` and `apps/web/sentry.server.config.ts`. `[PUBLIC]` ### Marketing Tags diff --git a/apps/web/.env b/apps/web/.env index eed6f93c4d..f52bd97508 100644 --- a/apps/web/.env +++ b/apps/web/.env @@ -10,13 +10,6 @@ NEXT_PUBLIC_POSTHOG_KEY=phc_GK2Pxl0HPj5ZPfwhLRjXrtdz8eD7e9MKnXiFrOqnB6z POSTGRES_CONNECT_TIMEOUT=10000 POSTGRES_MAX_QUERY_TIME=20000 -SENTRY_SUPPRESS_GLOBAL_ERROR_HANDLER_FILE_WARNING=1 - -# Sentry -SENTRY_ORG="kilo-code" -SENTRY_PROJECT="kilocode-web" -NEXT_PUBLIC_SENTRY_DSN="https://27ef80847dcd5e044283c8f88d95ffc9@o4509356317474816.ingest.us.sentry.io/4509565130637312" - # Google Waitlist stuff GOOGLE_SHEETS_SPREADSHEET_ID=1FheQ3Yk_NlT8rn010KSLK8NUk6nxYAE_At2ebCv5O8o GOOGLE_SERVICE_ACCOUNT_EMAIL=kilocode-waitlist-service@kilo-code-cli-waitlist.iam.gserviceaccount.com diff --git a/apps/web/instrumentation-client.ts b/apps/web/instrumentation-client.ts deleted file mode 100644 index f5e107d088..0000000000 --- a/apps/web/instrumentation-client.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { captureRouterTransitionStart, init } from '@sentry/nextjs'; - -if (process.env.NODE_ENV !== 'development') { - init({ - dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, - sendDefaultPii: false, - normalizeDepth: 5, - // Tracing is fully disabled. - tracesSampleRate: 0, - // Note: if you want to override the automatic release value, do not set a - // `release` value here - use the environment variable `SENTRY_RELEASE`, so - // that it will also get attached to your source maps - }); -} - -// This export will instrument router navigations, and is only relevant if you enable tracing. -// `captureRouterTransitionStart` is available from SDK version 9.12.0 onwards -export const onRouterTransitionStart = captureRouterTransitionStart; diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index 58b87813b8..14d389ed06 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -188,8 +188,6 @@ const nextConfig = { // This is required to support PostHog trailing slash API requests skipTrailingSlashRedirect: true, - // Maximize chance of decent client-side stack traces - productionBrowserSourceMaps: true, // Configure `pageExtensions` to include markdown and MDX files pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'], @@ -248,9 +246,6 @@ const sentryConfig = { // Only print logs for uploading source maps in CI silent: !process.env.CI, - // Upload a larger set of source maps for prettier stack traces (increases build time) - widenClientFileUpload: true, - // Tree-shake Sentry debug statements to reduce bundle size bundleSizeOptimizations: { excludeDebugStatements: true, diff --git a/apps/web/src/app/global-error.tsx b/apps/web/src/app/global-error.tsx index fcbee59bf5..4d9263a1b8 100644 --- a/apps/web/src/app/global-error.tsx +++ b/apps/web/src/app/global-error.tsx @@ -1,13 +1,6 @@ 'use client'; -import { captureException } from '@sentry/nextjs'; -import { useEffect } from 'react'; - -export default function GlobalError({ error }: { error: Error & { digest?: string } }) { - useEffect(() => { - captureException(error); - }, [error]); - +export default function GlobalError() { return (
diff --git a/apps/web/src/app/payments/topup/success/page.tsx b/apps/web/src/app/payments/topup/success/page.tsx index 1c5f4bfd7a..15e4c14bce 100644 --- a/apps/web/src/app/payments/topup/success/page.tsx +++ b/apps/web/src/app/payments/topup/success/page.tsx @@ -5,7 +5,6 @@ import { redirect } from 'next/navigation'; import { useEffect, useState } from 'react'; import { fetchCreditTransactionIdForStripeSession, getPaymentReturnUrl } from './actions'; import BigLoader from '@/components/BigLoader'; -import { captureMessage } from '@sentry/nextjs'; import { fromMicrodollars } from '@/lib/utils'; import { TOPUP_AMOUNT_QUERY_STRING_KEY } from '@/lib/organizations/constants'; import { PageContainer } from '@/components/layouts/PageContainer'; @@ -38,14 +37,6 @@ export default function TopUpSuccessPage() { // we intententionally listen to tries because we want to set a timeout after each try if (tries > 14) { setHasExceededMaxTries(true); - void (async function () { - captureMessage('Exceeded max tries to fetch credit transaction ID', { - extra: { - sessionId: sessionId, - }, - }); - })(); - return; } diff --git a/apps/web/src/components/auth/FakeLoginForm.tsx b/apps/web/src/components/auth/FakeLoginForm.tsx index c23447a0d9..b7e59f88bc 100644 --- a/apps/web/src/components/auth/FakeLoginForm.tsx +++ b/apps/web/src/components/auth/FakeLoginForm.tsx @@ -4,7 +4,6 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { signIn } from 'next-auth/react'; import { Button } from '../Button'; import getSignInCallbackUrl from '@/lib/getSignInCallbackUrl'; -import { captureException } from '@sentry/nextjs'; export type FakeSignInButtonProps = { searchParams: NextAppSearchParams; @@ -46,14 +45,6 @@ export function FakeLoginForm({ searchParams }: FakeSignInButtonProps) { }); } catch (error) { console.error('Fake login error:', error); - captureException(error, { - tags: { source: 'fake_login' }, - extra: { - email: submissionEmail, - callbackUrl, - }, - level: 'warning', - }); } finally { setIsLoading(false); } diff --git a/apps/web/src/components/auth/StytchClient.tsx b/apps/web/src/components/auth/StytchClient.tsx index d12e2c8527..d28f2cf14d 100644 --- a/apps/web/src/components/auth/StytchClient.tsx +++ b/apps/web/src/components/auth/StytchClient.tsx @@ -2,7 +2,6 @@ import Script from 'next/script'; import { useEffect, useState } from 'react'; -import { captureException } from '@sentry/nextjs'; const telemetryJsUri = process.env.NEXT_PUBLIC_STYTCH_PROJECT_ENV === 'test' @@ -41,7 +40,7 @@ export const StytchClient = ({ children }: React.PropsWithChildren) => { submitURL: 'https://auth.kilo.ai/submit', }) .then(setFreshTelemetryId) - .catch(err => captureException(err)); + .catch(error => console.error('Failed to get Stytch telemetry ID:', error)); }} /> {children} diff --git a/apps/web/src/hooks/useSignInFlow.ts b/apps/web/src/hooks/useSignInFlow.ts index b0466ac5e8..73c3252524 100644 --- a/apps/web/src/hooks/useSignInFlow.ts +++ b/apps/web/src/hooks/useSignInFlow.ts @@ -3,7 +3,6 @@ import { useState, useCallback, useMemo, useEffect, useRef } from 'react'; import { signIn } from 'next-auth/react'; import getSignInCallbackUrl from '@/lib/getSignInCallbackUrl'; -import { captureException } from '@sentry/nextjs'; import type { AuthProviderId } from '@/lib/auth/provider-metadata'; import { ProdNonSSOAuthProviders } from '@/lib/auth/provider-metadata'; import { useSignInHint, type SignInHint } from '@/hooks/useSignInHint'; @@ -307,9 +306,6 @@ export function useSignInFlow({ setShowTurnstile(false); } catch (error) { console.error('[SignInForm] Error during email check:', error); - captureException(error, { - tags: { source: 'email_lookup' }, - }); setIsVerifying(false); setShowTurnstile(false); setError('An error occurred. Please try again.'); @@ -389,9 +385,6 @@ export function useSignInFlow({ } } catch (error) { console.error('[SignInForm] Magic link request failed:', error); - captureException(error, { - tags: { source: 'magic_link_request' }, - }); setError('Failed to send magic link. Please try again.'); } }, [email, params, saveHint]); @@ -477,9 +470,6 @@ export function useSignInFlow({ setFlowState('landing'); } catch (error) { console.error('[SignInForm] Error during sign-in flow:', error); - captureException(error, { - tags: { source: 'turnstile_verification' }, - }); setError('An error occurred. Please try again.'); setShowTurnstile(false); setFlowState('landing'); @@ -539,9 +529,6 @@ export function useSignInFlow({ await signIn(provider, { callbackUrl }); } catch (error) { console.error('[SignInForm] OAuth sign-in failed:', error); - captureException(error, { - tags: { source: 'oauth_signin' }, - }); setError('Failed to sign in. Please try again.'); setFlowState('provider-select'); } diff --git a/apps/web/src/lib/ai-gateway/auto-model/index.ts b/apps/web/src/lib/ai-gateway/auto-model/index.ts index 8c13bb7d96..62f14f28cf 100644 --- a/apps/web/src/lib/ai-gateway/auto-model/index.ts +++ b/apps/web/src/lib/ai-gateway/auto-model/index.ts @@ -6,7 +6,7 @@ import { } from '@/lib/ai-gateway/providers/anthropic.constants'; import type { OpenRouterReasoningConfig } from '@/lib/ai-gateway/providers/openrouter/types'; import type { OpenCodeSettings, Verbosity } from '@kilocode/db/schema-types'; -import { QWEN37_PLUS_MODEL_ID } from '@/lib/ai-gateway/custom-pricing'; +import { QWEN37_PLUS_MODEL_ID } from '@/lib/ai-gateway/providers/qwen'; import { NVIDIA_TRIAL_TOS } from '@/lib/ai-gateway/providers/nvidia'; type AutoModel = { diff --git a/apps/web/src/lib/ai-gateway/custom-pricing.ts b/apps/web/src/lib/ai-gateway/custom-pricing.ts index 1f08bc5425..3e4fab2140 100644 --- a/apps/web/src/lib/ai-gateway/custom-pricing.ts +++ b/apps/web/src/lib/ai-gateway/custom-pricing.ts @@ -1,14 +1,14 @@ import { captureMessage } from '@sentry/nextjs'; import type { OpenRouterModel } from '@/lib/organizations/organization-types'; import type { JustTheCostsUsageStats } from '@/lib/ai-gateway/processUsage.types'; +import { QWEN37_MAX_MODEL_ID, QWEN37_PLUS_MODEL_ID } from '@/lib/ai-gateway/providers/qwen'; import { calculateCost_mUsd, type Pricing, type PricingTiers, } from '@/lib/ai-gateway/providers/kilo-exclusive-model'; -export const QWEN37_MAX_MODEL_ID = 'qwen/qwen3.7-max'; -export const QWEN37_PLUS_MODEL_ID = 'qwen/qwen3.7-plus'; +export { QWEN37_MAX_MODEL_ID, QWEN37_PLUS_MODEL_ID }; // Qwen long-context pricing starts at exactly 256 Ki tokens (262,144 tokens). const TOKENS_256K = 256 * 1024; diff --git a/apps/web/src/lib/ai-gateway/models.ts b/apps/web/src/lib/ai-gateway/models.ts index d661f69979..7864ae1151 100644 --- a/apps/web/src/lib/ai-gateway/models.ts +++ b/apps/web/src/lib/ai-gateway/models.ts @@ -29,8 +29,7 @@ import { GEMINI_PRO_CURRENT_MODEL_ID, gemma_4_26b_a4b_it_free_model, } from '@/lib/ai-gateway/providers/google'; -import { qwen36_plus_stealth_model } from '@/lib/ai-gateway/providers/qwen'; -import { QWEN37_PLUS_MODEL_ID } from '@/lib/ai-gateway/custom-pricing'; +import { QWEN37_PLUS_MODEL_ID, qwen36_plus_stealth_model } from '@/lib/ai-gateway/providers/qwen'; import { stepfun_37_flash_free_model } from '@/lib/ai-gateway/providers/stepfun'; import { isGrokModel } from '@/lib/ai-gateway/providers/xai'; import { isClaudeModel } from '@/lib/ai-gateway/providers/anthropic.constants'; diff --git a/apps/web/src/lib/ai-gateway/providers/qwen.ts b/apps/web/src/lib/ai-gateway/providers/qwen.ts index 8f07833e60..f4d2b6ff62 100644 --- a/apps/web/src/lib/ai-gateway/providers/qwen.ts +++ b/apps/web/src/lib/ai-gateway/providers/qwen.ts @@ -45,6 +45,9 @@ function makeTieredPricing( const TOKENS_256K = 256 * 1024; +export const QWEN37_MAX_MODEL_ID = 'qwen/qwen3.7-max'; +export const QWEN37_PLUS_MODEL_ID = 'qwen/qwen3.7-plus'; + export const qwen36_plus_stealth_model: KiloExclusiveModel = { public_id: 'stealth/qwen3.6-plus', display_name: 'Stealth: Qwen3.6 Plus (50% off)', diff --git a/apps/web/src/lib/auth/send-magic-link.ts b/apps/web/src/lib/auth/send-magic-link.ts index 014aa38205..72d58d9afb 100644 --- a/apps/web/src/lib/auth/send-magic-link.ts +++ b/apps/web/src/lib/auth/send-magic-link.ts @@ -1,5 +1,3 @@ -import { captureException } from '@sentry/nextjs'; - export type SendMagicLinkResult = { success: true } | { success: false; error: string }; /** @@ -33,7 +31,6 @@ export async function sendMagicLink( }; } catch (error) { console.error('Magic link request error:', error); - captureException(error, { tags: { source: 'magic_link_request' } }); return { success: false, error: 'Failed to send magic link. Please try again.', diff --git a/apps/web/src/lib/security-headers.test.ts b/apps/web/src/lib/security-headers.test.ts index 9bbcb7084a..356c90bac2 100644 --- a/apps/web/src/lib/security-headers.test.ts +++ b/apps/web/src/lib/security-headers.test.ts @@ -3,8 +3,6 @@ import { getConfiguredConnectSrcOrigins, getContentSecurityPolicyHeaderName, getContentSecurityPolicyMode, - getSecurityPolicyReportingHeaders, - getSentrySecurityReportUri, } from '@/lib/security-headers'; function getPolicyDirective(policy: string, directive: string): string { @@ -82,8 +80,6 @@ describe('security headers', () => { NEXT_PUBLIC_CLOUD_AGENT_NEXT_WS_URL: 'wss://next-agent.example.com/path', NEXT_PUBLIC_SESSION_INGEST_WS_URL: 'wss://ingest.example.com/path', NEXT_PUBLIC_GASTOWN_URL: 'https://gastown.example.com/api', - NEXT_PUBLIC_SENTRY_DSN: - 'https://27ef80847dcd5e044283c8f88d95ffc9@o4509356317474816.ingest.us.sentry.io/4509565130637312', }; expect(getConfiguredConnectSrcOrigins(env)).toEqual([ @@ -92,7 +88,6 @@ describe('security headers', () => { 'wss://ingest.example.com', 'https://gastown.example.com', 'wss://gastown.example.com', - 'https://o4509356317474816.ingest.us.sentry.io', ]); }); @@ -104,56 +99,6 @@ describe('security headers', () => { ).toEqual(['http://localhost:8787', 'ws://localhost:8787']); }); - it('adds Sentry security policy reporting directives when DSN is configured', () => { - const policy = buildContentSecurityPolicy({ - env: { - NEXT_PUBLIC_SENTRY_DSN: - 'https://27ef80847dcd5e044283c8f88d95ffc9@o4509356317474816.ingest.us.sentry.io/4509565130637312', - SENTRY_ENVIRONMENT: 'production', - SENTRY_RELEASE: 'web-2026-04-24', - }, - }); - - const reportUri = - 'https://o4509356317474816.ingest.us.sentry.io/api/4509565130637312/security/?sentry_key=27ef80847dcd5e044283c8f88d95ffc9&sentry_environment=production&sentry_release=web-2026-04-24'; - - expect(policy).toContain(`report-uri ${reportUri}`); - expect(policy).toContain('report-to csp-endpoint'); - expect(policy).toContain('https://o4509356317474816.ingest.us.sentry.io'); - }); - - it('builds Sentry security policy reporting headers', () => { - const reportUri = getSentrySecurityReportUri({ - NEXT_PUBLIC_SENTRY_DSN: - 'https://27ef80847dcd5e044283c8f88d95ffc9@o4509356317474816.ingest.us.sentry.io/4509565130637312', - VERCEL_ENV: 'preview', - }); - - expect(reportUri).toBe( - 'https://o4509356317474816.ingest.us.sentry.io/api/4509565130637312/security/?sentry_key=27ef80847dcd5e044283c8f88d95ffc9&sentry_environment=preview' - ); - - const headers = getSecurityPolicyReportingHeaders({ - NEXT_PUBLIC_SENTRY_DSN: - 'https://27ef80847dcd5e044283c8f88d95ffc9@o4509356317474816.ingest.us.sentry.io/4509565130637312', - VERCEL_ENV: 'preview', - }); - - expect(headers['Reporting-Endpoints']).toBe(`csp-endpoint="${reportUri}"`); - expect(JSON.parse(headers['Report-To'] ?? '')).toEqual({ - group: 'csp-endpoint', - max_age: 10886400, - endpoints: [{ url: reportUri }], - include_subdomains: true, - }); - }); - - it('omits Sentry security policy reporting when DSN is missing', () => { - expect(getSentrySecurityReportUri({})).toBeNull(); - expect(getSecurityPolicyReportingHeaders({})).toEqual({}); - expect(buildContentSecurityPolicy({ env: {} })).not.toContain('report-uri'); - }); - it('supports enforcement, report-only, and off modes', () => { expect(getContentSecurityPolicyMode({})).toBe('report-only'); expect(getContentSecurityPolicyHeaderName('report-only')).toBe( diff --git a/apps/web/src/lib/security-headers.ts b/apps/web/src/lib/security-headers.ts index 087fbbf28b..9cddc760b4 100644 --- a/apps/web/src/lib/security-headers.ts +++ b/apps/web/src/lib/security-headers.ts @@ -1,13 +1,10 @@ export type ContentSecurityPolicyOptions = { isDevelopment?: boolean; connectSrcUrls?: Array