English | Espanol
-
Verifica que la cache del catalogo se haya escrito:
ls -la "${COMMANDCODE_STATE_DIR:-$HOME/.local/state/opencode}/commandcode-models-cache.json" -
Si el archivo no existe, el plugin nunca llego a la API. Comprueba que la key se resuelve y que el endpoint responde:
curl -s -o /dev/null -w '%{http_code}\n' \ -H "Authorization: Bearer $(cat ~/.config/commandcode/key.txt)" \ -H 'User-Agent: cli' \ https://api.commandcode.ai/provider/v1/models
Un
200significa que el plugin deberia poblar la cache en el proximo arranque. -
Los modelos que listaste a mano en
opencode.jsonno se ven afectados: siguen registrados aunque el descubrimiento se omita. Una cache vacia no es un error. -
Los modelos Claude estan ausentes a proposito. Solo exponen
/messagesy responden400 unsupported_modelen el endpoint compatible con OpenAI. Ver mas abajo.
La familia claude-* en Command Code tiene forma Anthropic:
{"error":{"message":"Model \"claude-sonnet-5\" must be called via /provider/v1/messages (Anthropic Messages shape).","type":"invalid_request_error","code":"unsupported_model"}}El proveedor esta registrado como @opencode/ai/providers/openai-compatible, que habla
/chat/completions. El descubrimiento omite estos modelos por ese motivo. Si igual
ves el error, el modelo fue agregado a mano en opencode.json; quitalo de ahi o
enrutalo por un provider de forma Anthropic.
Este es el rechazo generico para el que existe el sanitizador. Revisa si fue un problema de payload o algo distinto:
tail -20 "${COMMANDCODE_STATE_DIR:-$HOME/.local/state/opencode}/commandcode-sanitizer.log"- Hay entradas cerca del fallo - el sanitizador reparo el payload y el request fue
rechazado por otra razon. El array
fixeslista exactamente que se reescribio; un rechazo posterior a una reparacion normalmente significa que el error upstream no tiene que ver con la estructura de las llamadas de herramienta. - No hay ninguna entrada - el request estaba estructuralmente limpio para el sanitizador. Causas comunes: un modelo que ya no existe (ver la seccion de tiers retirados), un campo no soportado en el cuerpo, o un request que el plugin nunca vio porque otro plugin lo reconstruyo antes.
El sanitizador es fail-open: si lanza una excepcion, el request original se envia sin cambios, asi que una linea faltante en el log tambien puede significar que la ruta de reparacion fallo. No hace falta habilitar nada extra para eso; el diseno se traga los errores de hook a proposito.
Los tiers gratuitos se retiran sin aviso y el id sigue resolviendo:
{"error":{"message":"The free Ling 3.1 Flash tier has been retired. Ling 3.1 Flash is now a paid model ...","type":"permission_error","code":"FORBIDDEN"}}Despues, la API expone el id de pago (por ejemplo inclusionai/ling-3.1-flash), y el
descubrimiento lo toma en el proximo arranque. Corrige una entrada obsoleta en
opencode.json alineandola con /provider/v1/models:
curl -s -H "Authorization: Bearer $(cat ~/.config/commandcode/key.txt)" \
-H 'User-Agent: cli' https://api.commandcode.ai/provider/v1/models \
| grep -o '"id": *"[^"]*"' | sortSi un modelo listado ahi igual falla, la promo termino entre tu request y el refresco del catalogo. Reselecciona y continua.
El log de reintentos es una huella forense, no un contador de fallos:
grep -c . "${COMMANDCODE_STATE_DIR:-$HOME/.local/state/opencode}/commandcode-retries.log"
grep -o '"type":"[^"]*"' "${COMMANDCODE_STATE_DIR:-$HOME/.local/state/opencode}/commandcode-retries.log" \
| sort | uniq -c | sort -rnQue significa cada entrada:
type |
Significado |
|---|---|
provider.transport (ECONNRESET) |
socket obsoleto o cortado; se reintenta de forma nativa |
provider.internal (503) |
sobrecarga upstream; se reintenta de forma nativa |
provider.invalid-output |
el stream termino sin finish_reason |
provider.invalid-request |
HTTP 400 del proveedor |
OpenCode reintenta los fallos de transporte transitorios por su cuenta (10 intentos,
backoff de 2s a 10s), asi que una rafaga recuperada nunca llega al usuario. La
inyeccion de Connection: close elimina una causa concreta (Bun reutilizando un socket
que Cloudflare ya cerro); no elimina los resets originados del lado upstream durante una
caida. Medido en un host de produccion: 99 de 130 eventos fueron resets transport
repartidos en diez dias, concentrados en rafagas cortas upstream, con cero sesiones sin
recuperar.
Para medir el efecto de la inyeccion, deshabilitala y compara:
export CC_NO_CONNECTION_CLOSE=1 # se restaura la reutilizacion de keep-aliveDesde la v1.0.2 el log de reintentos registra el modelo y el intento, asi que los resets se pueden atribuir a un modelo concreto en lugar de a un bloque indistinto de sesion:
# resets por modelo
grep -o '"modelID":"[^"]*"' <state>/commandcode-retries.log | sort | uniq -c | sort -rn
# el intento que fallo y el delay de backoff que eligio el runtime
grep '"modelID"' <state>/commandcode-retries.log | tail -5| Campo | Significado |
|---|---|
modelID / providerID |
Modelo y proveedor del request fallido |
attempt |
Numero de intento (1 es el request original) |
delayMs |
Delay de backoff elegido por el runtime antes del siguiente intento |
agent |
Agente que emitio el request |
Ojo: el backoff propio del runtime va de 2s a 10s. Huecos mucho mayores que delayMs entre
dos lineas del log significan que el request fallido tardo eso en romperse: es un timeout
upstream, no un socket keep-alive obsoleto.
El watchdog aborta un stream SSE mudo despues de 60s:
cat "${COMMANDCODE_STATE_DIR:-$HOME/.local/state/opencode}/commandcode-watchdog.log"- Existe una entrada
watchdog.timeout- el abort se disparo como esta disenado y el turno fallo rapido en lugar de congelarse. - No hay entrada ni archivo - el cuelgue ocurrio antes del primer chunk, ventana que el watchdog no vigila a proposito. Esa ventana pertenece al timeout del proveedor en OpenCode. Verifica que el request llego a la red, luego sube ese timeout o reintenta.
La cuenta de la key no tiene saldo, asi que Command Code rechaza todo request antes de enrutarlo, incluidos los modelos de pago. El plugin agrega una pista al mensaje y, si hay un pool de keys configurado, registra un cooldown de 24h para esa key.
Acciones: recarga la cuenta en commandcode.ai, o agrega una segunda key al pool
(COMMANDCODE_API_KEYS, COMMANDCODE_KEYS_FILE o
~/.config/commandcode/keys.txt). Ver
CONFIGURACION.
El modelo existe pero el plan no lo incluye (por ejemplo los modelos Claude en un
plan INDIVIDUAL-GOAT, o GPT-5.5 en Goat). El plugin agrega la pista "pick
another model or upgrade the plan" y deja el status intacto.
Revisa la lista vigente en commandcode.ai/models antes de subir de plan.
Configura un pool de dos o mas keys; la rotacion es opt-in y aditiva:
| Status | Cooldown registrado |
|---|---|
402 |
24h |
401 / 403 |
24h |
429 |
Retry-After hasta 6h, si no 5m |
5xx |
ninguno |
Revisa <state>/commandcode-keys-state.json para el estado y
<state>/commandcode-rotation.log para la linea de tiempo. Borra el archivo de
estado para limpiar todos los cooldowns de inmediato.
Con una sola key el plugin mantiene el comportamiento anterior y nunca
sobrescribe una cabecera Authorization que el host ya haya puesto.
ZDR es un opt-in explicito, el mismo que lee el CLI de Command Code:
export CMD_ZDR=1Cualquier otro valor (incluido true) lo deja apagado, y una cabecera
x-cmd-zdr enviada por el cliente se elimina para que una sesion no pueda
activarlo por accidente. La cabecera solo se envia al host estricto de Command
Code.
La herramienta de cuota responde
Error consultando Command Code: No se encontro .... Revisa que almacen ve el
resolvedor:
bun -e 'import { resolveApiKeyWithSource } from "./src/auth/resolver.ts";
console.log(resolveApiKeyWithSource().source);'none significa que los seis almacenes quedaron vacios. La solucion mas simple es la
variable de entorno:
export COMMAND_CODE_API_KEY="tu-key"Nota que el plugin sigue funcionando sin key: sanitizador, watchdog y cache del catalogo siguen operativos, solo se omite la inyeccion de credencial.
Cada campo se obtiene de forma independiente y un fallo degrada a N/D en lugar de
matar el reporte. Un reporte que muestra plan y ventanas pero sin totales de ciclo
significa que solo fallo /alpha/usage/summary. Reintenta; si la cuenta se creo hace
poco, el endpoint de resumen puede estar vacio los primeros minutos.
Sintoma: OpenCode loguea un id de plugin duplicado o commandcode_quota aparece dos
veces. Causa: un segundo plugin que registra la misma herramienta o los mismos hooks
(por ejemplo el plugin legacy commandcode-quota) sigue listado en opencode.json.
Quita la entrada vieja; correr ambos significa que los requests se sanitizan dos veces
y la segunda pasada ve un payload ya reparado.
El plugin es fail-open, asi que los fallos de telemetria son silenciosos. Verifica que el directorio de estado sea escribible:
mkdir -p "${COMMANDCODE_STATE_DIR:-$HOME/.local/state/opencode}" && touch "$_/probe" && rm "$_/probe"Un log de sanitizador vacio es el estado normal: solo recibe una entrada cuando un payload realmente necesito reparacion.
OpenCode carga plugins desde dos lugares: el array plugins de opencode.json y
todo directorio dentro de ~/.config/opencode/plugins/ (auto-descubrimiento). Esa
asimetria explica los dos casos confusos:
| Sintoma | Causa |
|---|---|
Un plugin que quitaste de plugins sigue apareciendo en opencode plugin list |
Vive en ~/.config/opencode/plugins/ y lo toma el scan de directorio |
Un plugin renombrado a .disabled dentro de ese directorio igual se carga |
El scan toma cualquier subdirectorio, no solo archivos .ts ni el nombre original |
Que funciona y que no:
# NO lo descarga
# - quitar la entrada de opencode.json
# - renombrar el directorio dentro de plugins/
# - reescribir opencode.json para forzar un reload
# SI lo descarga (el watcher lo suelta en vivo)
mv ~/.config/opencode/plugins/tu-plugin ~/fuera-del-directorio/tu-pluginVerifica con:
opencode plugin listSi el plugin sigue listado despues de moverlo, el servicio en ejecucion lo mantuvo en
memoria: reinicia el servidor en background (opencode service stop) y volve a abrir
OpenCode. Nota que opencode service restart por si solo tambien sirve, pero corta la
conexion del TUI actual.
Dos plugins registrando commandcode_quota no es fatal - el sanitizador es idempotente,
asi que la segunda pasada devuelve modified: false, y el tool se expone una sola vez -
pero duplica el trabajo de los hooks y enturbia la telemetria, asi que mantene una sola
copia instalada.