Skip to content

fix(terminal): drop dependencies=[] on WS include — silently 403s upgrades#28

Closed
Leolebleis wants to merge 2 commits intomainfrom
fix/ws-deps-propagation
Closed

fix(terminal): drop dependencies=[] on WS include — silently 403s upgrades#28
Leolebleis wants to merge 2 commits intomainfrom
fix/ws-deps-propagation

Conversation

@Leolebleis
Copy link
Copy Markdown
Owner

Root cause (via systematic debugging)

FastAPI's `app.include_router(..., dependencies=[Depends(...)])` silently rejects WebSocket upgrades with HTTP 403 — regardless of whether the dep itself raises. PR #26 (APIKeyHeader → Header) and #27 (move auth inline) only addressed the auth dep; the remaining `_require_terminal_enabled` dep on `include_router` was still triggering the silent rejection.

Verified by removing the whole `dependencies=[...]` arg: WS upgrade returns 101 immediately and the handler enters. Adding any dep back reverts to 403, even one that doesn't raise.

Fix

Drop `dependencies=[...]` entirely on the terminal WS router include. Both gates (api-key + `FELLOW_CHAT_ENABLED && FELLOW_MCP_ENABLED`) are now inline in the WS handler, evaluated at request time. HTTP routers are unchanged.

Verified

  • Good key: `HTTP/1.1 101 Switching Protocols` ✓
  • Bad key: `403` ✓
  • Test suite: 373 passed
  • Lint + type-check: clean

Leolebleis added 2 commits May 3, 2026 10:59
…rades

Root cause traced via systematic debugging: FastAPI's include_router
dependency-list propagation silently rejects WebSocket upgrades with HTTP
403 even when the deps don't raise. PR #27 removed require_api_key from
the dependencies list (after PR #26 fixed APIKeyHeader for WS), but the
remaining _require_terminal_enabled dep still triggered the rejection.

Fix: drop the entire dependencies=[...] arg on the WS router include and
move both gates (api-key + chat/MCP enabled) inline in the WS handler.
The HTTP routers continue to use dependencies=[Depends(require_api_key)]
unchanged — only the WS route is special.

Side effects:
- _require_terminal_enabled removed from main.py (unused)
- _chat_enabled module flag removed (only the gate read it)
- terminal/router.py owns its own gate via os.getenv at request time
tmux refuses to attach when TERM is unset or "dumb", emitting
"open terminal failed: terminal does not support clear" — the actual
output the user sees in the browser. uvicorn's process env has no TERM,
so the PTY child inherited none.

Set TERM=xterm-256color (what xterm.js emulates, ships in ncurses-base
which python:3.13-slim has) in os.environ before exec.

Self-tested end-to-end through the WS — 726 bytes of real tmux output
flowed through, no error.
@Leolebleis Leolebleis closed this May 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant