OpenWork 0.18.60+ rompió los modelos gratuitos y el archivado: causa raíz, análisis del bundle y reversión a 0.18.59 + motor v1
Actualizado el 29/10/2026. El techo de versión recomendado aquí era erróneo: la
0.18.59no es la última compilación que respeta el motor v1 — lo es la0.18.58. El porqué, y cómo la política de engine del Den devuelve la app al v2, está en El motor v2 vuelve solo en OpenWork.
Cuando OpenWork Desktop se actualizó solo a la versión 0.18.62, dos cosas se rompieron al mismo tiempo, sin ningún aviso en la interfaz:
-
Todos los modelos gratuitos (
deepseek-v4.1-flash-free,glm-5.3-flash-free,big-pickle, etc.) — tanto los de OpenCode Zen como los gratuitos accedidos con clave propia y billing activo enopencode.ai— empezaron a fallar con:json { "type": "error", "error": { "type": "FreeTierError", "message": "OpenCode's free tier can only be used from within OpenCode. Download it at https://opencode.ai and keep building." } } -
El botón de archivar sesión desapareció de la barra lateral, y las conversaciones archivadas anteriormente quedaron bloqueadas en modo solo lectura.
Quien intentó editar el opencode.jsonc, activar "Allow OpenCode Zen models" en el panel Den o volver solo el motor de chat a v1 manteniendo la app en 0.18.60+ (como se reporta en los issues different-ai/openwork#6408, #6466, #6467 y #6367) chocó con la misma pared: nada de eso devuelve los modelos.
Después de tres días inspeccionando el bundle del renderer, las bases SQLite locales y los endpoints HTTP del motor versión por versión, aislé la causa exacta de cada síntoma y el procedimiento completo de reversión.
1. La trampa principal: dos palancas independientes (versión de la app vs. motor de chat)
La mayoría de los intentos de corrección falla porque la actualización automática cambió dos variables independientes al mismo tiempo. Parecen un único bug de migración, pero responden a palancas diferentes:
| Síntoma | Qué controla realmente | Por qué tocar la config no resuelve |
|---|---|---|
El proveedor opencode desaparece de la UI y los modelos *-free fallan con 403 FreeTierError |
Versión de la App Desktop (0.18.59 vs. >= 0.18.60) |
Inyectado por la app desktop en la configuración gestionada en cada arranque/recarga del motor |
El botón de archivar desaparece (501) y el estado de proveedores se vacía |
Motor de Chat (v1 1.18.30 vs. v2 2.0.26) |
Bloqueo en el endpoint del preview v2 (/opencode2) y directorio de estado aislado |
Probando los instaladores versión por versión, la frontera quedó probada:
- La
0.18.59ya incluye el binario del motorv2.0.26, y en ella el proveedoropencodey los 45 modelos gratuitos funcionan normalmente. - El bloqueo empieza estrictamente en la
0.18.60. Por eso, forzar el motorv1siguiendo en la app0.18.60o0.18.62devuelve solo el archivado — el proveedoropencodesigue bloqueado por la versión de la aplicación.
2. Cómo la 0.18.60+ bloquea el proveedor opencode (4 capas)
A partir de la 0.18.60 (con el rollout removeOpencodeZen del PR #6340), la app desktop cierra el acceso a opencode en cuatro lugares simultáneos:
Capa 1 — El modal de la interfaz (Connect a provider)
En la 0.18.59, abrir Connect a provider (settings.provider.add) muestra OpenCode (opencode) como la primera fila de la lista, permitiendo guardar una OPENCODE_API_KEY (ock_… / oc_sk_…) o usar el fallback nativo de Zen ("Free models keep working without a key"). A partir de la 0.18.60, el ítem OpenCode desaparece por completo de esa lista — no hay cómo seleccionarlo ni conectar una clave por la UI.
Capa 2 — disabled_providers en la config gestionada del runtime
La app escribe "disabled_providers": ["opencode"] en los archivos gestionados del motor (%APPDATA%\OpenWork\runtime-opencode-config.json y %APPDATA%\openwork\opencode-v2\state\config\opencode.json).
Capa 3 — Eliminación del bloque providers gestionado
Cuando la cuenta está conectada a una organización en Cloud, el objeto providers gestionado proyecta solo el AI Gateway de la organización (ipr_<id>) y openwork-free (el modelo fijo detrás de la opción "Auto", openai/gpt-6-luna).
Capa 4 — El plugin provider-filters con whitelist vacía
Aunque GET /api/provider en el motor todavía muestre el proveedor embebido opencode con enabled: true, la llamada GET /api/config revela la regla inyectada en info.plugins[]:
{
"package": "file:///.../state/provider-filters-plugin",
"options": {
"providers": {
"opencode": {
"whitelist": []
}
}
}
}
Como whitelist es un array vacío ([]), el plugin ejecuta rule.whitelist !== undefined && !rule.whitelist.includes(model.id) y elimina todos los modelos opencode de GET /api/model. Editar el archivo JSON a mano no sobrevive: la app reescribe el archivo en el próximo arranque o recarga del motor. Y el flag "allowZenModel": true del panel Den, aunque siga llegando en el /v1/me/desktop-config, simplemente no es leído por el desktop.
Por qué los modelos "Free" del AI Gateway de la organización también fallan (403 FreeTierError)
Si el administrador habilita modelos *-free dentro del AI Gateway (ipr_<id>), aparecen en el selector. Sin embargo, al llamarlos, la solicitud pasa por el proxy del Gateway en lugar de salir con la credencial local del cliente OpenCode. El portón de opencode.ai rechaza cualquier llamada al tier gratuito que no venga autenticada por el propio cliente, devolviendo 403 FreeTierError ("OpenCode's free tier can only be used from within OpenCode").
3. Por qué el motor v2 bloquea el archivado de sesiones
La lectura del bundle del renderer (app-dist/assets/app-*.js) muestra el bloqueo exacto:
const IT = "Archiving and unarchiving are not available in the OpenCode v2 preview.";
const Rt = Xr(endpointUrl) ? IT : undefined; // Xr(e) prueba si el pathname termina en "/opencode2"
Y en el binario del motor v2, cinco operaciones devuelven HTTP 501 UnsupportedInV2Preview:
session.archivesession.commandsession.revert.partsession.shellsession.summarize
Como la copia de historial v1 → v2 conserva el campo time_archived en la tabla session_v2, cualquier conversación que estaba archivada en el v1 nace en solo lectura en el v2 ("This session is archived. Restore it before sending."), y el v2 se niega a desarchivarla (501).
4. Paso a paso de la reversión: app 0.18.59 + motor v1
Mientras los modelos gratuitos no vuelvan a servir en una versión >= 0.18.60, volver a la 0.18.59 y fijar el chat en el motor v1 devuelve tanto el catálogo completo de opencode como el archivado.
Paso 1 — Reinstalar OpenWork 0.18.59
Antes de buscar el instalador fuera de la máquina en Windows, revisa la carpeta de caché del updater:
Get-ChildItem "$env:LOCALAPPDATA\@openworkdesktop-updater" -Recurse
Mientras el instalador openwork-win-x64-0.18.62.exe queda guardado en la subcarpeta pending\, el archivo installer.exe en la raíz de %LOCALAPPDATA%\@openworkdesktop-updater\installer.exe frecuentemente todavía es el de la versión anterior (0.18.59):
(Get-Item "$env:LOCALAPPDATA\@openworkdesktop-updater\installer.exe").VersionInfo.FileVersion
# -> 0.18.59
Basta ejecutar ese installer.exe para reinstalar la 0.18.59 encima e ignorar/borrar el ejecutable de pending\.
Paso 2 — Desactivar las actualizaciones automáticas
En cuanto abra la 0.18.59, desactiva las actualizaciones automáticas en la configuración para impedir que la app vuelva sola a la 0.18.62.
Paso 3 — Fijar el enrutamiento de chat en el motor v1
Asegúrate de que el archivo %APPDATA%\OpenWork\engine-v2-preview.json tenga chatRouting: false y engineOrigin: "manual":
{
"enabled": true,
"chatRouting": false,
"engineOrigin": "manual"
}
(Nota: "enabled": true solo no cambia el motor cuando "chatRouting": false y "engineOrigin": "manual" están definidos.)
Paso 4 — Verificar el estado del runtime
Versión del binario instalado:
(Get-Item "$env:LOCALAPPDATA\Programs\@openworkdesktop\OpenWork.exe").VersionInfo.FileVersion
# -> 0.18.59
Motor activo en %APPDATA%\OpenWork\engine-instances.json: el campo bin de la instancia primary debe terminar en opencode-x86_64-pc-windows-msvc.exe (v1 1.18.30), y no en opencode2-....
Sin filtros inyectados en %APPDATA%\OpenWork\runtime-opencode-config.json:
"disabled_providers": []"providers": null(o{})- Ningún
provider-filtersen la lista"plugin".
Catálogo restaurado: GET http://127.0.0.1:<porta>/api/model (usando el puerto y el authProbe de engine-instances.json) vuelve a devolver todos los 46 modelos opencode (45 gratuitos), y el modal Connect a provider vuelve a listar OpenCode (OPENCODE_API_KEY) arriba.
5. Tres trampas posteriores al downgrade (y por qué no son fallas de la reversión)
Después de volver a la 0.18.59 + v1, dos avisos en la interfaz pueden dar la impresión de que algo se rompió. La lectura del código en el bundle (app-*.js) y del log del motor muestra qué ocurre realmente en cada uno:
Trampa 1 — Falso positivo "OpenWork Connect: Needs attention — The selected provider/model was not found in OpenCode provider catalog."
Lo que aparece: un aviso persistente en el menú de la cuenta / tarjeta de Connect, incluso con el chat y las tools del Cloud MCP funcionando normalmente. Al recargar el motor, el aviso desaparece por unos segundos (checking) y vuelve.
Por qué ocurre (rastreo en el bundle): la sonda de salud de Connect (_wn) lee w.prefs.defaultModel (openwork.defaultModel en el localStorage) y verifica si ese par provider/model existe en /api/model del motor activo. Sin embargo, no existe un botón en la interfaz de la 0.18.59 para cambiar w.prefs.defaultModel directamente — elegir un modelo en el selector del composer (DO) cambia solo el modelo de la conversación abierta (Fl.getState().setModel) o, sin conversación abierta, la tabla por workspace (workspace_default_models en %APPDATA%\OpenWork\runtime.sqlite). Además, cada vez que la app sincroniza los proveedores de la organización con OpenWork Cloud, corre la función N9e:
function N9e(cloudOptions, ctx) {
const filtered = XT(cloudOptions, ctx);
if (
ctx.currentDefault &&
(isCloudProvider(ctx.currentDefault.providerID) ||
filtered.some(r => r.providerID === ctx.currentDefault?.providerID && r.modelID === ctx.currentDefault.modelID))
) return null;
const firstCloud = filtered.find(r => isCloudProvider(r.providerID));
return firstCloud ? { providerID: firstCloud.providerID, modelID: firstCloud.modelID } : null;
}
donde isCloudProvider (pd) coincide con /^(lpr|ipr)_/i o "openwork". Si openwork.defaultModel no es un proveedor de nube, N9e fuerza la escritura del primer modelo del AI Gateway (ipr_.../gwm_...); y, una vez escrito ipr_..., N9e devuelve null y nunca más lo devuelve a un modelo local. Como el motor v1 no carga proveedores ipr_* en /api/model, la sonda de Connect siempre acusa ese desajuste.
Qué hacer: ignorarlo. Es un falso positivo estructural cuando hay sesión en Cloud con el motor v1. Si algún workspace específico abre conversaciones nuevas en un modelo muerto del Gateway, basta actualizar su fila en workspace_default_models del %APPDATA%\OpenWork\runtime.sqlite a {"providerID":"opencode","modelID":"deepseek-v4.1-flash-free"}.
Trampa 2 — "The free starter model is busy right now. Retrying..." es en realidad cuota HTTP 429 (FreeUsageLimitError)
Lo que aparece: los modelos gratuitos dejan de responder de repente y muestran un spinner:
The free starter model is busy right now. Retrying... To keep working now, connect your own model provider.
Por qué ocurre (bundle + log del motor): esto no es el bloqueo 403 FreeTierError de la 0.18.60. Inspeccionando el final de %USERPROFILE%\.local\share\opencode\log\opencode.log, aparece la respuesta real del servidor opencode.ai:
level=ERROR message="stream error" providerID=opencode modelID=deepseek-v4.1-flash-free
error.error="AI_APICallError: Free usage limit reached. Free models work in OpenCode (https://opencode.ai); subscribe to OpenCode Go to keep going: https://opencode.ai/go"
Cuando el sidecar recibe FreeUsageLimitError (HTTP 429), emite un estado de retry con action.reason = "free_tier_limit". En el bundle de OpenWork (RetryMessage / hVe), cualquier retry con reason === "free_tier_limit" tiene el título original ("Free limit reached") reescrito como "The free starter model is busy right now. Retrying…".
Cómo funciona la cuota por dentro (medido el 25/10): el portón del free tier es por IP, no por cuenta — una clave nueva no devuelve cuota (es lo que explica el 403 FreeTierError de quien intenta sondear desde fuera). El contador es un balde diario en Redis, ip:<ip>:<AAAA-MM-DD>, y los modelos con rateLimit propio cambian la fecha por un balde del modelo. El FAQ de Go documenta 200 solicitudes por día; en 22 y 23/10 la misma IP pasó 903 y 939 solicitudes completadas, y el 26/10 la primera negativa llegó en la 168.ª — el número es config de servidor y cambia sin changelog. El segundo techo que existe es el presupuesto de tokens por IP, vitalicio de los modelos servidos por proveedores de prueba (promoTokens): agotado, el modelo deja de preferir ese proveedor y sigue por otro — no genera 429 y no explica ventanas de minutos.
Qué hacer:
- Revisar las últimas líneas de
~/.local/share/opencode/log/opencode.logpara confirmar si esFree usage limit reached(429) y noFreeTierError(403). - No perder tiempo con una clave nueva: la cuota es de la IP. Lo que devuelve acceso es esperar el balde diario (cambia a la medianoche UTC, 21:00 en Brasilia) o el presupuesto de tokens del trial.
- Cambiar de modelo free compra minutos, no una salida: los baldes por modelo abren ventanas cortas (~5 min) y se cierran solos, sin consumo en el intervalo.
- Conectar tu propia API key de
opencode.ai(con billing activo) en Connect a provider → OpenCode libera al instante todos los modelos pagos — y no pone a cero el contador de los gratuitos.
Trampa 3 — Cómo contar el consumo diario de cuota free localmente
Como ni la interfaz de OpenWork ni el Console de OpenCode muestran un contador de cuota restante (y el export JSON de la pestaña Activity del Console trunca silenciosamente en 1.000 líneas con "truncated": true), el único contador fiel es la propia base SQLite del motor (%USERPROFILE%\.local\share\opencode\opencode.db en el v1, tabla message): se cuentan las filas role = "assistant" de modelos *-free / big-pickle sin campo error en el día UTC. En el v1, la solicitud rechazada también se puede leer allí — guarda finish: null con tokens en cero, mientras la que pasó guarda finish: stop. Conviene saber qué no mide ese número: la cuenta es de solicitudes, y el segundo techo (tokens del trial) no aparece en ella.
6. Resumen del trade-off (y el disparador para volver a actualizar)
Quedarse en la 0.18.59 + Motor v1 con actualizaciones automáticas desactivadas congela la versión del desktop y renuncia a lo que solo existe en el v2 (sync de proveedores en vivo, cambio de modelo de una sesión existente vía affordance, edición de skill sin recargar el motor). Sin embargo, como la app >= 0.18.60 bloquea incondicionalmente los modelos gratuitos de opencode, esa es hoy la única combinación que mantiene tanto el catálogo opencode como el archivado funcionando.
El disparador para volver a actualizar la app es uno solo: en cuanto una versión >= 0.18.60 devuelva el acceso al proveedor opencode y a los modelos gratuitos (seguido en los issues #6466, #6467 y #6408).
Bruno Torres
Emprendedor digital solo. Construyo negocios digitales desde cero con agentes de IA — y documento el camino, incluido lo que no funcionó.
Comentarios
Compartir: X · Facebook · LinkedIn · WhatsApp · Telegram · E-mail ·