Spring til indhold
DAagentic-workflowengineering-judgment

Gaten er dig

AI'ens første fix virkede. Det rigtige fix krævede tre gange 'jeg er ikke overbevist'. Hvem vandt? Ham der solgte tokens.

Scoret for

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:

human promptprompt
1 lines2026-07-02 17:53
there is something here that i am not sure we are doign right - if we are behind a proxy the heads and forwrded headers should solve this, i have not seen anything before were we need to "stripInternalPort" ouurself -can we review this and investigate if we are doing something wrong?

(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:

human promptprompt
1 lines2026-07-02 17:59
no i want to itterate abit further here, we could put in a very dum http request logger dotnet app in the apphost and deploy to really confirm your assumptions on ACA - because i am not convinced yet - how did you get to the conclussion ? are we sure or is it worth doing a assumption validation as i mention?

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:

human promptprompt
3 lines2026-07-02 19:01
please do a research online on this topic - i think we aredoign something wrong if we need to strip internal port. There must be some configuraiton, also do research of the source code of the intl package if thats what is doing it. And i think its something we have solved in the past - and for .net the middleware around forwarded headers is basically rewriting the urls internally as one of the first steps so a package later like international is not reading those headers itself but trusts that the incomring urls have been written corret first - i think thats much more clean package and we should consider doing that kind of middlewarewhere instead ? Maybe we have done it in some of the other proxy/middleware.ts files and we can steal it. If we find a good solution lets put it into one of the nextjs skills.

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?

human promptprompt
1 lines2026-07-02 19:16
but is that something we should look at, whats the expected pattern here when something hosts a docker container. we run nextjs on localhost:3000 and something is routing it in, dont we expect that to have the host to the public url that is used? it seems odd that a solution like intl is not figuring that out correctly. Its not even a x-forwarded headers issue is it?

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.