\ufeff, og det er et hint.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.
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.
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:
$ 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'
0 ting byttet ut. Samme ni regler som vask_logg.py, i samme rekkefølge. Teksten forlater ikke nettleseren din.
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.
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]
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:
$ 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:
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.
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.
Kjør den billigste testen først, ikke nødvendigvis den øverste. Test 2 og 3 er samme kommando:
$ 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:
$ 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:
$ 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:
$ 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:
$ 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.
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.
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.
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.
"""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.