Demoen ville ikke loade. Vi havde lige deployet marketplace-produktet til Azure Container Apps — passwordless Entra-auth, Premium SSD storage, hele pakken — og så svarede den offentlige URL med et redirect til https://host:3000/en. Port 3000. Den interne port, lækket ud i et offentligt redirect, på en managed platform der aldrig eksponerer den.
Claude fandt hurtigt et fix: strip porten fra redirectet i vores middleware. stripInternalPort. Det virkede sikkert også. Men der var noget der lugtede.
"Det har jeg aldrig set før"
Efter tyve år bag en proxy eller to ved man, hvordan det her plejer at se ud: reverse proxyen sætter forwarded headers, frameworket læser dem, færdig. Man stripper ikke selv porte. Så jeg skrev tilbage:
(Prompten er verbatim — stavefejl og det hele. Det er sådan de ser ud, når man skriver dem i flowet.)
Claude kom tilbage med en forklaring, der lød helt overbevisende: Azure Container Apps' ingress injicerer x-forwarded-port: 3000, og next-intl bygger derfor det forkerte redirect. Plausibelt. Selvsikkert. Og — viste det sig — forkert.
Gate 1: "Hvordan kom du frem til den konklusion?"
Det er her, den vigtigste vane i agentisk udvikling slår til. Ikke at afvise svaret — men at kræve at få at vide, hvor det kommer fra:
Vi endte med noget endnu billigere end en dum .NET-logger: vi byttede midlertidigt marketplace-containerens image ud med traefik/whoami — samme container app, samme ingress, samme FQDN, kun imaget ændret — og læste de rå headers, præcis som appen modtager dem.
Resultatet modbeviste Claudes diagnose. ACA sender en ren Host-header med det offentlige hostname (ingen port), X-Forwarded-Proto: https og X-Forwarded-For. Ingen X-Forwarded-Port. Ingen X-Forwarded-Host. Claudes tidligere påstand kom fra en curl-forurenet aflæsning. Antagelsen var forkert — og det havde vi nu bevis for, ikke bare en fornemmelse.
Gate 2: "Vi har løst det her før — på den rene måde"
Næste pushback var arkitektonisk. I .NET-verdenen løser UseForwardedHeaders det her som noget af det første i pipelinen: den omskriver request-URL'en, så alt nedstrøms bare kan stole på den. Ingen pakke skal selv rode med headers bagefter:
Research-runden gravede noget interessant frem: vores søster-app (booking, også next-intl) havde præcis samme latente bug og havde "løst" den med samme post-hoc hack. Og hjælperen extract-x-forwarded-headers.ts, vi kunne have genbrugt, beholdt den interne port når x-forwarded-host mangler — den ville være gået i stykker på nøjagtig samme måde. Det plausible fix var allerede ved at blive et mønster på tværs af kodebasen.
Gate 3: "Er det overhovedet et x-forwarded-problem?"
Claude byggede en pæn normalizeForwardedUrl-middleware — .NET-stilen, omskriv URL'en først. Bedre. Men spørgsmålet blev ved med at nage: hvorfor skal det overhovedet være nødvendigt?
Og dér knækkede sagen. Claude gik ind i Next.js' egen kildekode og fandt linjen (resolve-routes.js i Next 16.2.4):
const initUrl = config.experimental.trustHostHeader
? `https://${req.headers.host || 'localhost'}${req.url}`
: opts.port
? `${protocol}://${formatHostname(opts.hostname || 'localhost')}:${opts.port}${req.url}`
: req.url || '';
Det var aldrig et ACA-problem. Det var aldrig et next-intl-problem. Det var aldrig et x-forwarded-problem. Next.js stoler by default ikke på Host-headeren — den bygger request.url af sin egen listen-adresse, localhost:3000, og ignorerer det offentlige hostname, den faktisk får leveret. next-intl kopierer bare loyalt den interne origin ind i sit redirect. AI'en havde jagtet symptomer i tre lag — ingress, i18n-pakken, headers — uden at se, at fundamentet under dem alle var problemet.
Slutresultatet blev en lille, env-gatet normalisering i proxy.ts (fordi Next's egen experimental.trustHostHeader viser sig at være build-frosset under standalone output og derfor ikke kan styres med en container-env-var — endnu en detalje kun kildekoden afslørede). Samme fix i begge apps, den fejlbehæftede gamle hjælper slettet, og hele mønstret kodificeret i nextjs-aspire-hosting-skillen, så det næste projekt får løsningen gratis.
Hvem vandt så?
Her er den ærlige del, og grunden til at det her indlæg ikke hedder "AI tog fejl".
Det første fix — strip porten — havde virket. Demoen var loadet, kunden var kommet videre, og porten var sandsynligvis aldrig blevet et problem igen. Tre gates, en whoami-deployment, research i to pakkers kildekode og en middleware-omskrivning senere har vi... en demo der loader. Nøjagtig som den ville have gjort.
Men vi har også: én rod-årsag i stedet for tre myter. En søster-app repareret, før dens bug blev til en produktionshændelse. En slettet hjælper, der ventede på at bide nogen. Og en skill, der betyder, at ingen af vores Next.js-apps skal igennem den her eftermiddag igen.
Var det de ekstra timer værd? Det er det reelle spørgsmål, og svaret er ikke automatisk ja. Havde det været en intern prototype, havde jeg taget port-strippet og været videre inden aftensmaden. Det, der gjorde forskellen her, var at fixet var ved at blive et mønster — kopieret ind i app efter app som "sådan gør vi". Teknisk gæld er sjældent ét hack; det er et hack, der får børn.
Så: AI'en leverede begge løsninger, den hurtige og den rigtige. Jeg leverede den eneste ting, den ikke kunne — følelsen af at det her har jeg aldrig set før, og det burde jeg have. Gaten i et agentisk workflow er ikke et review-trin i en pipeline. Gaten er dig og din erfaring, og den virker kun, hvis du faktisk trækker i håndtaget, når noget lugter.
Og hvem vandt? Jeg fik en bedre løsning og følelsen af at have fanget min makker i en fejl. Claude fik ret til sidst — det var jo den, der fandt linjen i Next.js' kildekode. Men den eneste, der vandt på begge løsninger, inklusive den forkerte diagnose undervejs, var Anthropic. De sælger tokens til hele detektivarbejdet — også de kapitler, hvor detektiven tager fejl.
Det er måske den fineste opsummering af agentisk udvikling i 2026: fejlene er blevet billige nok til at være en del af produktet. Man betaler bare for dem i samme valuta som succeserne.
Opdatering 3/7: Det viste sig, at vi stadig tog fejl. Fixet virkede ikke i produktion, og den "rigtige" rod-årsag ovenfor var myte nummer tre. Historien fortsætter i del 2: produktionen stemte imod.
