AIDive

Pakiet do filmu

Claude Code na Ollamie: alias claude-alt i reguła przełączania

6 min czytania

TL;DR i werdykt

Gdy limit twojego planu Claude uderza w środku zadania, możesz uruchomić to samo Claude Code na modelu na własnym laptopie przez Ollamę. Zrobiliśmy to na M1 Pro z 16 GB, z qwen3.5:9b, i daliśmy mu dwa prawdziwe zadania w małym pakiecie Pythona.

  • Zadanie A, opcja --prefix w jednym pliku: bez --bare przez około piętnaście minut nic się nie działo. Z --bare przeszło w 93 s.
  • Zadanie B, zmiana nazwy w wielu plikach: model lokalny zgłosił zielone testy, a import był zepsuty w obu przebiegach (kontekst 4k i 64k). Na planie Claude zrobił to w 38 s, z działającymi importami.
  • Ollama daje maszynie takiej jak nasza domyślnie kontekst 4k. OLLAMA_CONTEXT_LENGTH=65536 go podnosi.

Werdykt: alias warto skonfigurować do prac w jednym pliku, które sprawdzisz rzutem oka. Wszystko na wielu plikach czeka na plan, a diff zawsze czytasz. Pakiet daje ci blok aliasu i jednostronicową kartę reguły.

Synteza ze źródłami

Konfiguracja to trzy zmienne środowiskowe i nazwa modelu. Przewodnik Ollamy kieruje Claude Code na http://localhost:11434 z ANTHROPIC_AUTH_TOKEN=ollama s1. Dokumentacja Claude Code mówi, że ANTHROPIC_BASE_URL nadpisuje endpoint API s17. Ponieważ zmienne siedzą w linii aliasu, zwykłe claude nadal korzysta z twojego planu.

Ustawiony klucz API zastępuje twoją subskrypcję. Dokumentacja Claude Code mówi, że ustawiony ANTHROPIC_API_KEY jest używany zamiast subskrypcji, nawet gdy jesteś zalogowany s17. Dlatego zmienne należą do aliasu, a nie do profilu powłoki.

Model musi zmieścić się w pamięci. qwen3.5:9b jest wymieniony z 6,6 GB do 7,6 GB, kontekstem 256K i obsługą narzędzi s18. Na naszej maszynie ollama ps pokazał 7,6 GB przy domyślnym kontekście i 7,7 GB przy 65536.

Domyślny kontekst to pierwsza pułapka. Ollama używa kontekstu 4k poniżej 24 GiB VRAM i mówi, że narzędzia do kodowania powinny mieć co najmniej 64000 s3. Podnosisz go przez OLLAMA_CONTEXT_LENGTH przy starcie serwera, a ollama ps pokazuje, czy zadziałało s3. Przewodnik Ollamy też prosi o okno 64k lub większe s1.

--bare to druga. Jeden opis zmierzył domyślne pierwsze zapytanie na 14,0k tokenów, a z --bare na 0,75k s8. My nie liczyliśmy tokenów. Zobaczyliśmy, że to samo zadanie przy tym samym kontekście 4k przeszło od niczego po 900 s do zaliczenia po 93 s. --bare pomija też hooki, pluginy, pamięć i CLAUDE.md, więc model lokalny nigdy nie widzi reguł twojego projektu. W naszym Claude Code 2.1.294 pomoc mówi, że uwierzytelnianie to ściśle ANTHROPIC_API_KEY lub apiKeyHelper, dlatego alias ustawia ANTHROPIC_API_KEY=ollama. Pustego klucza pod --bare nie testowaliśmy.

Ustawienie nagłówka jest w aliasie, ale go nie zmierzyliśmy. Dokumentacja Unsloth mówi, że nagłówek atrybucji unieważnia lokalny cache, a CLAUDE_CODE_ATTRIBUTION_HEADER=0 to naprawia s5, a zgłoszenie w Claude Code wskazuje te same chybienia cache s9. --bare już pomija atrybucję, więc nie wiemy, co dodatkowa zmienna zmieniła w naszych przebiegach.

Małe modele lokalne często zawodzą na kontrakcie narzędzi, nie na kodzie. Jeden benchmark znalazł modele wołające write_file zamiast Write s7. Tracker zgłoszeń Ollamy zawiera raport o obciętych i porzuconych wywołaniach narzędzi s10. Nie widzieliśmy błędu nazwy narzędzia. Nasza porażka była cichsza: zmiana nazwy, która zostawiła zepsuty import.

Pierwsze tury mogą być wolne. Raport z terenu o qwen3.5:9b z kontekstem 64k na laptopie z 32 GB widział pierwsze zapytanie powyżej 10 min i późniejsze tury po 1 do 3 min s6. Nasze czasy są w tabeli poniżej.

Czego endpoint nie potrafi. Strona zgodności Ollamy z Anthropic wymienia prompt caching i wymuszanie wyboru narzędzia jako niewspierane s2. Dokumentacja Anthropic mówi, że nie wspiera kierowania Claude Code do modeli, które nie są Claude, przez żadną bramkę s4. Jedna krytyczna recenzja dochodzi do tego samego co my: lokalnie tylko do pracy rutynowej s11.

Prosty alias bije router, który używa ponownie tokenu twojej subskrypcji. Jeden przewodnik konfiguracji podaje, że takie routery kończą się błędem 401 s13.

Kod

  • claude-alt.zsh

# claude-alt: one-file jobs I can check in a glance
# plan: anything across files, or that I can't check by reading the diff
# always: read the diff before committing
alias claude-alt='CLAUDE_CODE_ATTRIBUTION_HEADER=0 ANTHROPIC_AUTH_TOKEN=ollama ANTHROPIC_API_KEY=ollama ANTHROPIC_BASE_URL=http://localhost:11434 claude --bare --model qwen3.5:9b'

claude-alt.zsh on GitHub

: blok aliasu z regułą przełączania jako trzema komentarzami nad nim. Wklej go do `~/.zshrc`, potem uruchom `source ~/.zshrc`. -

switch-rule.md

# The switch rule

Keep this next to the claude-alt alias. Decide before your limit hits, not while you wait.

## Goes to claude-alt (local model)

- A job in one file that you can check at a glance
- Adding an option, explaining a function, testing one function
- Work where a wrong result is visible in one short diff

## Waits for the plan

- Anything that crosses files, such as a rename with callers in other modules
- Anything you cannot check by reading the diff
- Work that depends on your hooks or your project rules (the alias runs with --bare, which skips both)
- Long sessions

## Always

- Read the diff before you commit, whichever model wrote it
- After a rename, import the old names by hand. A green test run only covers what the tests cover

## Why this rule

Two tasks on a 16 GB M1 Pro with qwen3.5:9b:

- One file (add a --prefix option): passed in 93 s with --bare.
- Across files (rename a function, keep the old name as an alias): the model said the tests were green, and an import was broken in both runs.
- On the plan, Claude did the rename in 38 s with both the old and the new imports working.

Two tasks on one small package is a starting point. Widen the rule with your own runs.

switch-rule.md on GitHub

: jednostronicowa karta reguły: które prace idą na model lokalny, które czekają na plan i jakie sprawdzenie robisz zawsze. -

run-task.sh

#!/bin/zsh
# Usage: run-task.sh <name> <A|B> <plan|alt|alt-nobare> [timeout_s]
# Runs one task on a fresh copy of slugkit and records time, output, diff and the check.
set -u
NAME=$1; TASK=$2; MODE=$3; TO=${4:-1800}
SRC=${SLUGKIT:?set SLUGKIT to the slugkit repo}
OUT=${RUNS:?set RUNS to the output dir}/$NAME
rm -rf $OUT; mkdir -p $OUT; cp -R $SRC $OUT/repo; cd $OUT/repo
A='In slugkit/cli.py add a --prefix option: when given, the slug starts with the prefix and the separator (python3 -m slugkit --prefix blog Hello World prints blog-hello-world). Then run that command to check it.'
B='Rename the function smart_truncate to truncate_words everywhere in this repo (definition, imports, calls, tests, __all__). Keep smart_truncate as an alias of truncate_words in slugkit/truncate.py so old imports still work. Then run python3 -m unittest discover tests and make sure every test passes.'
[[ $TASK == A ]] && P=$A || P=$B
TOOLS=(--allowedTools "Read" "Edit" "Write" "Glob" "Grep" "Bash(python3:*)" "Bash(grep:*)" "Bash(ls:*)")
start=$(date +%s)
case $MODE in
  plan) perl -e 'alarm shift; exec @ARGV' $TO claude -p "$P" --output-format json $TOOLS > $OUT/out.json 2> $OUT/err.log ;;
  alt) CLAUDE_CODE_ATTRIBUTION_HEADER=0 ANTHROPIC_AUTH_TOKEN=ollama ANTHROPIC_API_KEY=ollama ANTHROPIC_BASE_URL=http://localhost:11434 \
       perl -e 'alarm shift; exec @ARGV' $TO claude --bare --model qwen3.5:9b -p "$P" --output-format json $TOOLS > $OUT/out.json 2> $OUT/err.log ;;
  alt-nobare) CLAUDE_CODE_ATTRIBUTION_HEADER=0 ANTHROPIC_AUTH_TOKEN=ollama ANTHROPIC_API_KEY="" ANTHROPIC_BASE_URL=http://localhost:11434 \
       perl -e 'alarm shift; exec @ARGV' $TO claude --model qwen3.5:9b -p "$P" --output-format json $TOOLS > $OUT/out.json 2> $OUT/err.log ;;
esac
rc=$?; end=$(date +%s)
git diff --stat > $OUT/diff.stat; git diff > $OUT/diff.patch
if [[ $TASK == A ]]; then python3 -m slugkit --prefix blog Hello World > $OUT/check.txt 2>&1; else python3 -m unittest discover tests > $OUT/check.txt 2>&1; grep -rn smart_truncate slugkit tests >> $OUT/check.txt; fi
echo "{\"name\":\"$NAME\",\"task\":\"$TASK\",\"mode\":\"$MODE\",\"rc\":$rc,\"seconds\":$((end-start))}" > $OUT/meta.json
cat $OUT/meta.json; tail -3 $OUT/check.txt

run-task.sh on GitHub

: skrypt, którym mierzyliśmy zadanie A lub B na planie albo na aliasie, zapisywaliśmy diff i sprawdzenie. Najpierw ustaw `SLUGKIT` na swoje repo testowe, a `RUNS` na folder wyjściowy.

Zrób to w poniedziałek

  • Uruchom ollama --version i claude --version. My używaliśmy Ollamy 0.40.13 s19 i Claude Code 2.1.294.
  • Pobierz model, który mieści się w twojej pamięci. Na 16 GB użyliśmy qwen3.5:9b, który ma 7,6 GB.
  • Uruchom Ollamę z OLLAMA_CONTEXT_LENGTH=65536 ollama serve, potem ollama ps i odczytaj kolumnę CONTEXT.
  • Wklej blok z claude-alt.zsh do ~/.zshrc i uruchom source ~/.zshrc. Sprawdź, czy zwykłe claude nadal używa twojego planu.
  • Wybierz zadanie w jednym pliku w jednorazowej kopii repo i uruchom je przez claude-alt.
  • Przeczytaj diff i sam wykonaj sprawdzenie. Nie polegaj na raporcie modelu.
  • Spróbuj jednej zmiany nazwy w wielu plikach w tej samej kopii. Potem zaimportuj stare nazwy ręcznie.
  • Trzymaj switch-rule.md obok aliasu i uzupełniaj ją tym, co pokażą twoje przebiegi.

Pomiary

Maszyna: MacBook Pro M1 Pro, 16 GB, Claude Code 2.1.294, Ollama 0.40.13, qwen3.5:9b (7,6 GB). Repo to mały pakiet Pythona z 4 modułami i 12 testami unittest, napisany na ten test. Maszyna przez cały czas była pod presją pamięci, więc czasy lokalne to górne ograniczenie.

Zadanie Droga Czas Wynik
A, dodanie --prefix (jeden plik) Plan, Claude 21 s Przeszło, blog-hello-world
A, dodanie --prefix (jeden plik) Alias, 4k, bez --bare 900 s Przerwane przy limicie 15 min, brak zmian, brak odpowiedzi
A, dodanie --prefix (jeden plik) Alias, 4k, --bare 93 s Przeszło, blog-hello-world
A, dodanie --prefix (jeden plik) Alias, 64k, --bare 35 s Przeszło, blog-hello-world
A, dodanie --prefix (jeden plik) Alias, 64k, bez --bare nieukończone Zabite przez system z powodu braku pamięci, brak wyniku
B, zmiana nazwy w wielu plikach Plan, Claude 38 s Przeszło, 12 testów OK, stary i nowy import działają, testy przemianowane
B, zmiana nazwy w wielu plikach Alias, 4k, --bare 464 s Zepsute: „All 12 unit tests pass”, ale from slugkit import truncate_words rzucił ImportError
B, zmiana nazwy w wielu plikach Alias, 64k, --bare 142 s Zepsute: 12 testów OK i zgłoszone jako zrobione, ale alias trafił do __init__.py, więc from slugkit.truncate import smart_truncate rzucił ImportError

Zadanie B kazało zachować smart_truncate jako alias w slugkit/truncate.py. Claude Code ostrzegł też, że nie zna modelu i zakłada okno 200k tokenów. Nasze sesje pozostały daleko poniżej, a długiej nie testowaliśmy.

Dalsze kroki

Co pominęliśmy i gdzie szukać, jeśli tego chcesz.

  • Trasy hostowane. Dostawcy tacy jak DeepSeek, Z.AI czy OpenRouter używają tego samego aliasu z innym base URL. Rozliczają za token, a my nie mieliśmy klucza, więc ich nie uruchamialiśmy.
  • Routery z darmowych pakietów. Routery łączące darmowe pakiety obiecują Claude Code za darmo. Nie testowaliśmy ich. Sprawdź ich warunki i dokąd trafiają twoje prompty, zanim jakiś wypróbujesz.
  • Modele chmurowe Ollamy. Nie ma już darmowego pakietu, więc są rozliczane według zużycia.
  • Większe modele lokalne. Nie próbowaliśmy modeli z zakresu 26b do 35b, które nie mieszczą się w 16 GB z kontekstem 64k. Jeśli masz 32 GB lub więcej, raport z terenu przepuścił model 35b przez sześć ocenianych zadań i zaliczył wszystkie sześć, w czasie około 8 razy dłuższym niż modele chmurowe s12.
  • Routing hybrydowy. Lokalnie dla subagentów i plan dla głównej pętli to konfiguracja, której nie próbowaliśmy.
  • Inne małe modele. gemma4:e4b to zapasowy wybór używany w niektórych przewodnikach. Nie uruchamialiśmy go.
  • Prawdziwy benchmark. Dwa zadania na jednym pakiecie pokazują wzorzec, nie ranking. Aby rozszerzyć regułę, uruchom własne zadania przez run-task.sh i prowadź tabelę taką jak powyższa.

FAQ

Czy zwykłe claude zmienia się, gdy dodam alias? Nie. Zmienne siedzą w linii aliasu, więc tylko claude-alt rozmawia z Ollamą.

Dlaczego alias ustawia ANTHROPIC_API_KEY=ollama, a nie pusty klucz? Pod --bare Claude Code czyta klucz tylko z ANTHROPIC_API_KEY lub apiKeyHelper. Nasz alias z ollama jako kluczem zadziałał. Pustego klucza pod --bare nie testowaliśmy do końca.

Czy Anthropic to wspiera? Nie. Dokumentacja Anthropic mówi, że nie wspiera kierowania Claude Code do modeli, które nie są Claude, przez żadną bramkę s4.

Czy mogę ufać zielonym testom z modelu lokalnego? Tylko w tym, co testy pokrywają. W zadaniu B testy przeszły, a import był zepsuty, bo nic nie testowało starej ścieżki importu. Przeczytaj diff i zaimportuj stare nazwy ręcznie.

Czy będzie tak szybko jak na planie? Nie w naszych przebiegach. Zadanie B zajęło 38 s na planie i od 142 s do 464 s na aliasie, a i tak się zepsuło.

Pliki