자동완성이 안 뜬다 — 겹쳐 있던 네 가지 원인
지난 글에서 Tabby 서버를 겨우 띄운 다음 날, VS Code에서 회색 자동완성이 하나도 뜨지 않았다. 로그에는
Provided inline completion items: 0만 반복됐다. 원인은 하나가 아니라 네 겹이었다. 설정 혼선, Ollama의 알려진 버그, 재시작을 거듭하다 잠긴 DB, 그리고 서버를 다 고친 뒤에도 예전의 빈 응답을 재생하던 클라이언트 캐시.(사내 인프라 특성상 IP·서버 이름은 일반화했다.)
서버는 떴는데 자동완성이 안 뜬다
Tabby 서버는 정상이었다. curl localhost:8080도 응답했다. VS Code에 Tabby 확장(tabbyml.vscode-tabby 1.28.0 — 처음엔 프리릴리즈인가 의심했지만 정식 릴리즈였다)을 깔고 주소와 토큰을 넣었다.
그런데 코드를 쳐도 회색 텍스트가 뜨지 않는다. 확장 로그는 이 줄만 반복했다.
Provided inline completion items: 0
요청은 나가는 것 같은데 결과가 0개다. 여기서부터 한 겹씩 벗겼다.
네 겹 벗기기
[1차] 주소를 두 곳에 넣었다
Tabby VS Code 확장은 서버 주소를 두 군데서 읽을 수 있다.
- VS Code
settings.json의tabby.endpoint - 로컬 agent 설정
~/.tabby-client/agent/config.toml의[server]섹션
양쪽에 다 넣어 뒀는데, 어느 순간 settings.json 쪽 값이 빈 문자열("")로 바뀌어 있었다. 확인해 보니 실제로 적용되고 있던 건 agent 설정 쪽이었다. 원인이라기보다 헷갈리는 변수였으니, 주소와 토큰은 agent 설정 한 곳에만 두기로 정리했다.
토큰은 Tabby 웹 화면에 로그인하면 나오는 “Endpoint URL / Token” 페이지에서 받는다. 이 페이지는 주소를 localhost:8080으로 보여 주는데, 다른 PC에서는 통하지 않는 주소라 실제 서버 IP로 바꿔 넣어야 한다. 이 함정은 온보딩 가이드 편에도 적었다.
여전히 0개.
[2차] 200인데 비어 있다 — Ollama /v1/completions의 FIM 버그
자동완성 요청은 200으로 돌아오는데 내용이 비어 있었다. 모델 문제인지, Tabby가 요청을 잘못 만드는지 가르려고 Tabby를 거치지 않고 Ollama에 직접 FIM 프롬프트를 보냈다.
curl http://<LLM 서버 A>:11434/api/generate -d '{
"model": "qwen2.5-coder:14b",
"prompt": "<|fim_prefix|>def add(a, b):\n return a<|fim_suffix|>\n<|fim_middle|>",
"raw": true,
"stream": false
}'
` + b` 같은 완성 텍스트가 정상으로 왔다. 모델도, 프롬프트 형식도 멀쩡하다. 남은 건 그 사이, Tabby가 요청을 보내는 입구다.
Ollama 이슈 #5544에 있는 알려진 버그였다. OpenAI 호환 /v1/completions가 넘겨받은 FIM 프롬프트를 안에서 다시 채팅 템플릿으로 감싸 버려서 FIM 토큰이 힘을 잃는다. 임베딩 편에서 kind = "openai/completion"으로 붙인 게 이 버그를 그대로 맞았다. 자동완성만 Ollama 고유 API로 바꿨다.
[model.completion.http]
kind = "ollama/completion" # openai/completion 아님
model_name = "qwen2.5-coder:14b"
api_endpoint = "http://192.168.x.x:11434" # /v1 을 뺀다
prompt_template = "<|fim_prefix|>{prefix}<|fim_suffix|>{suffix}<|fim_middle|>"
버그는 /v1/completions(FIM) 쪽이라 채팅과 임베딩은 그대로 뒀다.
[3차] 이번엔 서버가 안 뜬다 — database is locked
이것저것 바꾸며 docker compose restart와 --force-recreate를 여러 번 반복하는 사이, Tabby가 쓰는 SQLite가 비정상 종료 흔적을 남겼다. 서버가 아예 뜨지 못했다.
Must be able to initialize db: database is locked
컨테이너를 완전히 내리고, 남아 있던 잠금·저널 파일만 지운 뒤 다시 올렸다.
docker compose down
sudo rm -f ~/.tabby/ee/db.sqlite-shm \
~/.tabby/ee/db.sqlite-wal \
~/.tabby/index/.tantivy-writer.lock \
~/.tabby/index/.tantivy-meta.lock
docker compose up -d
본체 db.sqlite와 백업 db.backup-*.sqlite는 건드리지 않는다. 그걸 지우면 계정과 인덱스가 통째로 날아간다. 서버는 다시 떴다.
[4차] 서버는 멀쩡한데 0개 — 클라이언트 캐시
이제 Tabby 서버에 직접 자동완성을 요청하면 ` + b`가 정상으로 온다. 그런데 VS Code에서는 여전히 0개였다. 로그를 다시 보니 이 줄이 있었다.
Completion cache hit
클라이언트가 캐시에서 답을 꺼내 쓰느라 서버에 요청을 보내지도 않고 있었다. 그 캐시에 든 건, 앞의 문제들로 서버가 망가져 있던 동안 같은 코드로 반복 테스트하며 받은 빈 응답이었다.
오늘 한 번도 쳐 보지 않은 새 코드로 테스트했다. Fetching more completions from the server...가 세 번 이어서 찍히며 진짜 요청이 나갔고, 마침내:
Provided inline completion items: 3
Tab으로 받아들이면 Tabby 서버로 POST /v1/events가 나가는 것까지 확인했다. VS Code 기본 자동완성이나 다른 확장이 아니라 Tabby가 만든 결과라는 증거다.
디버깅하는 동안에는 Alt+\(Trigger Inline Suggestion)로 요청을 하나씩만 보냈다. 타이핑 중 자동으로 나가는 요청이 취소되며 생기는 잡음을 줄이려는 것이고, 평소에는 타이핑을 잠깐 멈추면 자동으로 뜬다.
덤 — 느린 건 GPU를 안 써서였다
뜨긴 뜨는데 후보 하나에 1초 가까이, 세 개면 3초쯤 걸렸다. 백엔드에 물어봤다.
curl -s http://<LLM 서버 A>:11434/api/ps | jq '.models[] | {name, size_vram}'
qwen2.5-coder:14b의 size_vram이 0이었다. GPU를 전혀 쓰지 않고 CPU로 추론하고 있었다. 채팅 서버(qwen3-coder, 30.5B)도 똑같이 0이었다. 채팅이 상대적으로 빠르게 느껴졌던 건 GPU 때문이 아니라, 자동완성은 후보 세 개를 차례로 만들고 채팅은 답 하나만 만들기 때문이다.
Tabby 설정 문제가 아니라 LLM 서버 쪽 문제라 담당자에게 넘겼다. 담당자가 모델을 다시 올리는 사이 서버가 아예 내려가 Failed to connect가 됐고, 복구 직후 첫 요청은 모델을 메모리에 다시 올리느라 15초 넘게 걸렸다. 이럴 땐 다시 시도를 거듭하지 말고 1분쯤 손을 떼고 기다리는 게 낫다. 같은 맥락에서, 응답이 수 ms 만에 오는데 비어 있다면 “빠른 성공”이 아니라 백엔드에 연결조차 못 한 것일 가능성이 크다.
그 뒤
한 달이 안 돼 자동완성 백엔드는 Ollama에서 vLLM으로, 채팅은 llama.cpp로 바뀌었다. 그래서 2차의 ollama/completion 우회는 지금 구성에는 해당하지 않는다. GPU를 쓰지 않는 문제는 8월 기록까지도 풀리지 않은 채 남아 있었고, 지금 백엔드에서도 그런지는 다시 확인해야 한다.
마치며
진짜로 배운 것
- 여러 겹이 겹친 버그는 안쪽부터 벗긴다. 백엔드에 직접 curl → Tabby 서버에 직접 curl → 마지막에 IDE. IDE부터 만지면 변수가 너무 많고, “설정을 바꿨더니 됐다”는 착각에 빠지기 쉽다.
- 반복 테스트는 매번 새 코드로. 같은 입력을 반복하면 클라이언트 캐시가 예전 실패를 계속 재생한다. 서버는 멀쩡한데 클라이언트에서만 안 될 때 캐시를 의심한다.
- 200을 믿지 말고 내용을 본다. 200인데 비어 있으면 입구(엔드포인트) 문제일 수 있고, 너무 빠른데 비어 있으면 백엔드가 죽어 있을 수 있다.
- 재시작을 거듭했다면 잠금 파일을 본다.
database is locked면-shm·-wal·.lock만 지운다. 본체 DB는 건드리지 않는다.