web: cap_reached tells the operator a limit but never the count, and its remediation names no terminal they can find
- Dominant language
- Python
- Stars
- 2
- Forks
- 0
- Avg merge
- 15h 59m
- Merged PRs (30d)
- 9
Description
## Symptom
`remo web` refuses to open a terminal with:
```
[cap_reached] The per_client terminal limit (16) has been reached.
Close an existing terminal and try again.
```
…while the operator has, by their own count, nowhere near 16 terminals open. The
message is *correct* — but it is unfalsifiable from where the operator stands, so
the natural reading is "the service leaked slots" and the natural next step is a
restart. In the reported case the real cause was mundane (two browser tabs, each
minting its own attachment per pane — see the companion issue), and none of the
text pointed there.
## Why the current message can't be acted on
`src/remo_cli/web/api/terminals.py:215-222` builds the envelope from
`CapReachedError`'s `scope` and `limit` alone:
```python
except CapReachedError as exc:
return _error(
429,
"cap_reached",
f"The {exc.scope} terminal limit ({exc.limit}) has been reached.",
remediation="Close an existing terminal and try again.",
retryable=True,
)
```
Three gaps, in order of how much they cost the operator:
1. **It reports the limit but never the count, or what makes it up.** The
registry knows both — `_live_count(client_id)` produced the comparison one
line earlier, and the per-state breakdown is a `collections.Counter` away.
2. **"Close an existing terminal" doesn't say *which*, or *where*.** Terminals
the operator cannot see (another tab, a stale attachment from a dropped
socket) are exactly the ones holding the slots — so the advice describes an
action they cannot take.
3. **`scope` leaks an internal token.** `per_client` is a Python identifier, not
a thing the operator has a mental model of. It reads like a variable name
because it is one. The concept it names — "this browser's share of the
service's terminals, bucketed by IP" — is never explained.
The only way to see the truth today is `GET /api/v1/terminals`, which is
undocumented for this purpose and returns exactly the bucket the cap counts.
Diagnosing a 429 should not require knowing that.
## Suggested shape
Fold the count and its composition into the envelope, e.g.:
> The per-browser terminal limit (16) has been reached — you currently hold 16
> (14 ready, 2 pending). Terminals opened in **other browser tabs on this
> machine** count toward the same limit. Close some panes, or raise
> `REMO_WEB_TERMINAL_CAP_PER_CLIENT`.
Points worth pinning in whatever lands:
- Name the env var. `REMO_WEB_TERMINAL_CAP_PER_CLIENT` / `..._GLOBAL`
(`web/config.py:100-105`, `REMO_WEB_` prefix) are the supported escape hatch
and appear in no user-facing text.
- Say the caps are **in-memory and per-process**, so a service restart clears
them outright. The reporter's first hypothesis was that a count had survived
shutdown; nothing in the product contradicts that.
- Explain the bucket honestly. `client_id` is the peer IP
(`terminals.py:133-137`), a documented MVP stand-in for real auth — so behind a
reverse proxy or a Docker port-forward *every* client collapses into one
bucket and the per-client cap silently becomes a second global cap. An operator
hitting 16 in that topology has no way to deduce it.
## Acceptance
- A `cap_reached` 429 states the current count and its per-state breakdown.
- The remediation names the env var and the fact that other tabs share the count.
- `docs/web-session-interface.md` documents the caps, the `REMO_WEB_*` overrides,
their in-memory lifetime, and the reverse-proxy caveat.
- Tests cover the populated message, not just the 429 status.
Contributor guide
Research direction
Start in src/remo_cli/web/api/terminals.py:215-222, then read the count and client-ID handling around terminals.py:133-137 and the settings in web/config.py:100-105. Update the cap_reached response and add coverage for its count and per-state breakdown, then document the caps, REMO_WEB_* overrides, in-memory lifetime, and reverse-proxy caveat in docs/web-session-interface.md.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, documentation
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100