Be om fem hypoteser, ikke ett svar

Når koden krasjer, er det fristende å lime inn feilmeldingen og be om løsningen. Be heller om fem mulige årsaker du kan avkrefte, og stryk dem én om gangen. Del 4 av 4 i Koding med AI.

Magnus Gribbestad 8. oktober 2026 8 min
Figur · fem mistenkte
Fem hypoteser om en feil, strøket én etter én Øverst feilmeldinga KeyError: 'dato'. Fem mistenkte stiller opp foran en vegg med høydestreker, nummerert i den rekkefølgen AI-en rangerte dem: BOM-tegn, semikolon, stavemåte, tittelrad og annen kode. Tester i konsollen nederst stryker to, så to til. BOM-tegnet står igjen og bekreftes av de rå bytene i fila. Tegnkodingen rettes, og programmet viser snittfart 4:47 per km. KeyError: 'dato' <sti>/snitt.py:32 1BOM-tegn 2semikolon 3stavemåte 4tittelrad 5annen kode fem hypoteser, rangert rangeringen er en gjetning kolonnenavn['dato', 'km', 'tid'] → ikke semikolon, ikke stavemåte første linje'\ufeffdato,km,tid\n' → ingen tittelrad, men se \ufeff rå bytesb'\xef\xbb\xbfdato,km' → et usynlig BOM-tegn før «dato» rettetencoding="utf-8-sig" 3 økter · snittfart 4:47 per km
  1. Programmet krasjer med KeyError: 'dato'. Stien er vasket bort før feilmeldinga limes inn.
  2. Du ber om fem mulige årsaker, rangert, med en test for hver. Rangeringen er en gjetning. Testene er det du skal ha.
  3. Første test: hvilke kolonnenavn leser programmet? Svaret er riktig, så semikolon og stavemåte strykes.
  4. Første linje er overskriften, og koden rundt feilen ser ut som den skal. Tittelrad og annen kode strykes. Men linja starter med \ufeff, og det er et hint.
  5. Én står igjen. De rå bytene viser et usynlig BOM-tegn foran «dato». Tekstprogrammer viser det ikke.
  6. Én linje rettes, og programmet går. Du fant årsaken ved å avkrefte, ikke ved å tro på det første svaret.

Ber du om ett svar, får du ett svar. Det høres like sikkert ut enten det er riktig eller ikke. Ber du om fem mulige årsaker med en test for hver, får du noe du kan sjekke.

Dette er del 4 av 4 i serien Koding med AI. Del 1 handler om arbeidsflyten, del 2 om kodegjennomgang og del 3 om å skrive testene først. Her går vi gjennom en vanlig feil fra start til slutt. Eksempelet er konstruert, men feilen er vanlig, og alt er kjørt.

Hvorfor ett svar er feil bestilling

En språkmodell som får en feilmelding, foreslår det som pleier å forårsake slike feil. Det er ofte riktig. Men den ser ikke fila di, maskinen din eller dataene dine, og den sier ikke fra av seg selv når den gjetter. Retter du det første forslaget og feilen forsvinner, vet du fortsatt ikke om du rettet årsaken eller bare symptomet.

Et svar fra AI er en hypotese, ikke en fasit. Ber du om fem hypoteser, blir det tydelig. Ber du om en test for hver, kan du avkrefte dem én etter én til én står igjen. Det er ikke en AI-metode. Det er slik god feilsøking alltid har vært. AI-en gjør bare listen raskere å lage. Mer om hvorfor svar kan være feil og likevel høres sikre ut, står i Når AI tar feil.

Først: vask det du limer inn

Logger og feilmeldinger er fulle av ting som ikke skal ut av huset: nøkler, innloggingstokens, e-post, telefonnumre, IP-adresser, kundenumre og filstier med brukernavnet ditt. Bruk bare et AI-verktøy virksomheten har godkjent, og vask teksten før du limer den inn, også der.

Kjører du et skript på egen maskin, står gjerne brukernavnet ditt og navnene på mappene dine i stien i feilmeldinga. Det er det første vaskeskriptet fjerner. Skriptet er et grovfilter med ni vanlige mønstre: filstier, passord i adresser, Authorization-hoder, kjente tokenformater, nøkkel=verdi, e-post, IP-adresser, elleve siffer og norske telefonnumre:

Utskrift · kjørt 9. oktober 2026 · konstruert logg
$ python vask_logg.py logg_eksempel.txt
2026-10-08 07:14:02 INFO  Bruker <e-post> logget inn fra <ip>
2026-10-08 07:14:03 DEBUG Kaller API med Authorization: Bearer <token>
2026-10-08 07:14:03 DEBUG Konfig: api_key=<fjernet> timeout=30 GITHUB_TOKEN=<fjernet>
2026-10-08 07:14:04 ERROR Import feilet for kunde <11 siffer>, tlf <telefon>, mobil <telefon>
Traceback (most recent call last):
  File "<sti>/import.py", line 88, in les
KeyError: 'dato'

Vask en logg før du limer den inn

0 ting byttet ut. Samme ni regler som vask_logg.py, i samme rekkefølge. Teksten forlater ikke nettleseren din.

En vasker er aldri en garanti

Den finner det den har mønster for, og ingenting annet. Navn, adresser, kundenumre i ditt format og nøkler med et navn den ikke kjenner, slipper rett gjennom. Les den vaskede teksten selv, linje for linje, før du limer den inn.

Prompten: fem hypoteser, en test for hver

Prompt · Fem hypoteser
Her er en feilmelding og koden den kommer fra. Ikke gi meg en løsning ennå.
Gi meg fem mulige årsaker, rangert etter hvor sannsynlige du mener de er.
For hver årsak:
1. Hva som ville vært galt.
2. Én rask test jeg kan kjøre for å avkrefte den, med kommandoen.
   Testen skal bare lese, ikke endre noe.
3. Hva jeg ser hvis årsaken er feil, og hva jeg ser hvis den er riktig.
Si hva du antar, og hva du ville trengt å se for å bli sikrere.

Feilmelding (vasket):
[lim inn]

Kode:
[lim inn den delen feilen peker på]

Slik kjører jeg det: [kommandoen]
Anthropics dokumentasjon for Claude Code gir samme råd i kortform: si hvilken kommando som gir feilen, og be om rotårsaken, ikke at feilmeldinga skal bort.

Et eksempel du kan kjøre

Et lite skript leser en liste med løpeøkter fra en CSV-fil og regner ut snittfarta. Fila ser riktig ut når du åpner den, og første linje er dato,km,tid. Likevel krasjer skriptet, med en feil som sier at kolonnen dato ikke finnes:

Utskrift · kjørt 9. oktober 2026
$ python snitt.py okter.csv 2>&1 | python vask_logg.py
Traceback (most recent call last):
  File "<sti>/snitt.py", line 39, in <module>
    main()
    ~~~~^^
  File "<sti>/snitt.py", line 32, in main
    rader.sort(key=lambda r: r["dato"])
    ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<sti>/snitt.py", line 32, in <lambda>
    rader.sort(key=lambda r: r["dato"])
                             ~^^^^^^^^
KeyError: 'dato'

Ved en testkjøring 8. oktober 2026, under arbeidet med sida, gikk feilmeldinga, koden og kommandoen inn i prompten over, i en egen Claude-økt uten tilgang til fila. Svaret under er forkortet. Kjøringen er ikke gjentatt, det fullstendige svaret er ikke lagret, og et nytt forsøk gir et annet svar:

Svar fra én testkjøring 8. oktober 2026 · forkortet
AI

Først en ting som snevrer inn feltet: KeyError: 'dato' betyr at nøkkelen mangler i ordboka for raden. Feilen skyldes altså overskriftslinja, ikke dataene.

  1. BOM fra Excel. Et usynlig tegn først i fila blir med i navnet. Test: les de første bytene rått.
  2. Feil skilletegn. Norsk Excel bruker semikolon. Test: skriv ut kolonnenavnene.
  3. Navnet er ikke helt likt. «Dato», mellomrom eller hardt mellomrom. Test: samme som 2.
  4. Overskriften står ikke på første linje. Test: skriv ut de tre første linjene.
  5. Koden som kjører, er ikke koden du viste. Test: les linjene rundt linje 32.

Hva jeg antar: du er på Windows med norsk Excel. Hvis du åpnet fila i Excel, sier det ingenting om skilletegnet, og da rykker punkt 2 opp på førsteplass.

Rangeringen er modellens gjetning. Excel står ikke i prompten: at fila kommer fra Excel på Windows, antok modellen selv, og den antakelsen kan ha styrt rekkefølgen. Det er testene som avgjør, ikke rekkefølgen.

Stryk én om gangen

Kjør den billigste testen først, ikke nødvendigvis den øverste. Test 2 og 3 er samme kommando:

Test for hypotese 2 og 3 · kjørt
$ python -c "import csv; print(csv.DictReader(open('okter.csv', encoding='utf-8-sig', newline='')).fieldnames)"
['dato', 'km', 'tid']

Kolonnenavnene er riktige, så semikolon og stavemåte strykes. Men les testen før du stoler på den. Den åpner fila med utf-8-sig, og det er selve rettelsen for hypotese 1. Testen viser at skilletegn og stavemåte er i orden, men den skjuler samtidig det som er galt. Kjører du den uten -sig, slik skriptet faktisk leser fila, ser du det:

Samme test, slik skriptet leser · kjørt
$ python -c "import csv; print(csv.DictReader(open('okter.csv', encoding='utf-8', newline='')).fieldnames)"
['\ufeffdato', 'km', 'tid']

Det er det beste enkeltrådet på denne sida: en test kan inneholde svaret uten at du merker det. Les hver test og spør om den gjør noe annerledes enn programmet ditt.

Test 4 skriver ut første linje, lest slik skriptet leser den:

Test for hypotese 4 · kjørt
$ python -c "print(repr(open('okter.csv', encoding='utf-8').readline()))"
'\ufeffdato,km,tid\n'

Overskriften står på første linje, så det er ingen tittelrad over den, og hypotese 4 strykes. Men se på starten av linja: \ufeff står foran «dato». Testen avkrefter én hypotese og peker samtidig på en annen, hvis du ser etter. Test 5 er å lese linjene rundt linje 32 i snitt.py. Ingen kode endrer radene før sorteringen, så den strykes også. Én står igjen, og dens egen test bekrefter den:

Test for hypotese 1 · kjørt
$ python -c "print(open('okter.csv','rb').read(30))"
b'\xef\xbb\xbfdato,km,tid\r\n2026-10-01,10.'

De tre bytene \xef\xbb\xbf er et merke som ifølge Python-dokumentasjonen ble innført av Microsoft for Notisblokk. Python skriver det selv hvis du ber om utf-8-sig. Tekstprogrammer viser det ikke. Rettelsen er én linje, encoding="utf-8-sig", som hopper over merket hvis det er der:

Utskrift · kjørt 9. oktober 2026
$ python snitt_rettet.py okter.csv
3 økter fra 2026-10-01 til 2026-10-05
Snittfart: 4:47 per km

En felle i samme familie: lagrer du fra norsk Excel som vanlig CSV, og ikke som «CSV UTF-8», blir fila ofte lagret med Windows-tegnsettet cp1252 og med semikolon mellom kolonnene. Da hjelper ikke utf-8-sig, og skriptet krasjer på første æ, ø eller å. snitt_rettet.py sier fra om begge deler i stedet for å krasje, og vask_logg.py leser en cp1252-fil og sier at den gjorde det.

Arbeidsmåten, kort

  1. Vask feilmeldinga og loggene, og bruk et godkjent verktøy.
  2. Be om fem hypoteser, rangert, med en test som bare leser.
  3. Les testene før du kjører dem. Endrer de noe, eller inneholder de rettelsen?
  4. Kjør den billigste først, og stryk det den avkrefter.
  5. Lim inn resultatet og be om ny rangering hvis det er uklart.
  6. Når én står igjen: rett den, kjør på nytt, og skriv en test som ville fanget feilen. Det er del 3.

Kjør ikke kommandoer du ikke forstår

En test fra en AI er kode fra en kilde som kan ta feil. Testene i en feilsøking skal bare lese. Ser du noe som sletter, flytter, installerer eller sender noe, stopp og spør hva den gjør. Det gjelder dobbelt på en server.

Når ingen står igjen

Det skjer. Da er det noe du tok for gitt som ikke stemmer: programmet som kjører, er ikke det du tror, fila er en annen, eller feilen oppstår et annet sted enn meldinga sier. Lim inn alle fem testene med resultat, si hva som er avkreftet, og be om fem nye. Fem nye hypoteser med en liste over det som er utelukket, er mye bedre enn den første lista.

Er du leder, står det du bør spørre et utviklerteam om, også om logger og verktøy, samlet i del 2.

For deg som bygger: skriptene

Alt ligger i tech/losninger/kode/feilsok/: snitt.py med feilen, snitt_rettet.py, okter.csv med BOM-merket, vask_logg.py, test_vask_logg.py og logg_eksempel.txt. Bare standardbiblioteket.

test_vask_logg.py er en testliste med 29 tekster: det som skal vaskes bort, og det som skal stå, som datoer, key=lambda og vanlige adresser. Alle 29 er riktige med Python 3.13, 9. oktober 2026. Vaskeren i nettleseren er laget fra de samme reglene, og testlista er kjørt gjennom de mønstrene også, i .NET sin regex-motor, ikke i en nettleser. Legg til det dine egne logger inneholder.

vask_logg.py
"""Vasker en logg eller feilmelding før den limes inn i en AI-tjeneste.

    python vask_logg.py logg_eksempel.txt
    python snitt.py okter.csv 2>&1 | python vask_logg.py

Hører til gribben.no/tech/losninger/feilsok-med-ai.html. Bare standardbiblioteket.
Vaskeren i nettleseren på samme side bruker de samme ni reglene i samme
rekkefølge. Der \\w står her, står [\\p{L}\\p{N}_] i JavaScript, fordi \\w
i JavaScript bare kjenner a–z og ville latt æøå-adresser slippe halvveis.

Bytter ut det som oftest lekker: filstier med brukernavn, brukernavn og
passord i adresser, Authorization-hoder, kjente tokenformater, nøkkel=verdi,
e-post, IP-adresser, elleve siffer (som kan være fødselsnummer) og norske
telefonnumre. Rekkefølgen betyr noe: nøkler tas før de generelle
mønstrene, ellers blir en del av en nøkkel stående igjen.

Dette er et grovfilter, aldri en garanti. Det kjenner ikke navn, adresser,
kundenumre i ditt format eller nøkler med et navn det ikke har sett. Les
den vaskede teksten selv, linje for linje, før du limer den inn.
"""
import re
import sys

REGLER = [
    # 1. Filstier: behold filnavnet, fjern mappene. De sier hvem du er.
    (re.compile(r'(?:(?<![A-Za-z])[A-Za-z]:\\|/home/|/Users/)[^"\n]*[\\/]'), "<sti>/"),
    # 2. Brukernavn og passord i en adresse: https://ola:hemmelig@server
    (re.compile(r"(\w+://)[^/\s:@]+:[^/\s@]+@"), r"\1<fjernet>@"),
    # 3. Authorization-hoder: Bearer og Basic
    (re.compile(r"(?i)\b(bearer|basic)\s+[A-Za-z0-9._~+/=-]{8,}"), r"\1 <token>"),
    # 4. Tokens med kjent form: JWT, GitHub, AWS-nøkkel-ID, sk-nøkler, Slack
    (re.compile(r"\beyJ[\w-]+\.[\w-]+\.[\w-]+"
                r"|\b(?:ghp|gho|ghu|ghs|ghr|github_pat)_\w{20,}"
                r"|\bAKIA[0-9A-Z]{16}\b"
                r"|\bsk-[\w-]{20,}"
                r"|\bxox[abpr]-[\w-]{10,}"), "<token>"),
    # 5. nøkkel=verdi, også GITHUB_TOKEN=, SECRET_KEY: og "password": "...".
    #    «key» alene tas ikke, ellers forsvinner key=lambda fra Python-kode.
    (re.compile(r"(?i)([\w-]*(?:api[_-]?key|[_-]key|token|secret|passord|password|pwd|n[oø]kkel))"
                r"[\"']?\s*[=:]\s*(\"[^\"]*\"|'[^']*'|\S+)"), r"\1=<fjernet>"),
    # 6. E-post
    (re.compile(r"[\w.+-]+@[\w-]+(?:\.[\w-]+)+"), "<e-post>"),
    # 7. IPv4
    (re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b"), "<ip>"),
    # 8. Elleve siffer, også 6 + 5 med mellomrom, kan være et fødselsnummer
    (re.compile(r"(?<!\d)\d{6} ?\d{5}(?!\d)"), "<11 siffer>"),
    # 9. Norske telefonnumre: 3-2-3 eller 2-2-2-2, med eller uten +47.
    #    Bare vanlig mellomrom som skille, så datoer som 2026-10-08 får stå.
    (re.compile(r"(?<!\d)(?:\+47 ?)?(?:\d{3} ?\d{2} ?\d{3}|\d{2} ?\d{2} ?\d{2} ?\d{2})(?!\d)"), "<telefon>"),
]


def vask(tekst: str) -> str:
    for mønster, erstatning in REGLER:
        tekst = mønster.sub(erstatning, tekst)
    return tekst


def les_fil(sti: str) -> str:
    # Filer lagret fra norsk Excel eller eldre Windows-programmer er ofte
    # cp1252, ikke UTF-8. Da leses de som cp1252, og vi sier fra.
    try:
        with open(sti, encoding="utf-8-sig") as f:
            return f.read()
    except UnicodeDecodeError:
        print(f"Merk: {sti} er ikke UTF-8, leser den som cp1252 (Windows).", file=sys.stderr)
        with open(sti, encoding="cp1252") as f:
            return f.read()


if __name__ == "__main__":
    sys.stdout.reconfigure(encoding="utf-8")
    sys.stderr.reconfigure(encoding="utf-8")
    if len(sys.argv) > 1:
        inn = les_fil(sys.argv[1])
    else:
        # Fra en pipe: tegn som ikke er UTF-8, blir byttet ut, ikke krasj.
        sys.stdin.reconfigure(encoding="utf-8", errors="replace")
        inn = sys.stdin.read()
    print(vask(inn), end="")

Kjørt på nytt med Python 3.13 på Windows 9. oktober 2026. Feilmeldinga er vasket med vask_logg.py før den ble limt inn her.

Oppdatert oktober 2026. Claude Code-dokumentasjonen, lest 8. oktober 2026: Common workflows («Fix bugs efficiently»: oppgi kommandoen som gir feilen) og Best practices («address the root cause, don't suppress the error»). Python-dokumentasjonen, codecs (utf-8-sig og opphavet til BOM-merket i UTF-8). Hypotesene er fra én testkjøring 8. oktober 2026 under arbeidet med sida, forkortet, ikke gjentatt, og blir ikke like neste gang. Loggen, fila og feilen er konstruert. Nøkkelen, tokenet, e-postadressen og tallene i loggen er ikke ekte.