# Reported issues for Long Run Hybrid Coach

Pod holds 21 of 31 problems reported by people outside the maintainer team. Issues filed by the project's own owners, members and collaborators are excluded entirely — a maintainer's release checklist is not a warning to a prospective user.

Back to [Long Run Hybrid Coach](/mcp/long-run-hybrid-coach).

## Most discussed

### Agent entries: finish the Custom GPT mobile loop and add a hosted MCP server

## The contract every entry inherits

A Custom GPT has no durable memory of prior conversations. That is acceptable only because
**the product memory lives outside the agent**:

```text
current PlanState + DecisionEvent history + latest actuals
```

Every new chat reconstructs continuity by calling the gateway and reading that same state.
No entry owns a second plan, second memory, second validator, second coaching rules engine,
or provider-specific state. Every entry is a thin adapter around th

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/35) · 2026-08-15 · closed · 4 comments

### Gateway security review follow-ups: public origin, OAuth conformance, volume guard, rotation

Record of the 2026-08-26 gateway security review. **This body is the current follow-up list; #15 owns priority.**

## Current status — 2026-08-26

### Completed

- **Legacy `/v1/coach/*` REST surface — DONE via #292.** The 22 routes that could accept a raw Intervals provider credential as identity were deleted. Socket-level HTTP tests moved to `/mcp`, behavior tests use the internal route dispatch, and regressions assert all retired paths return 404 without authenticating or touching owner state

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/288) · 2026-08-26 · closed · 3 comments

### OpenClaw / ClawHub distribution: package canonical Skill + MCP endpoint for chat-based agent users

## Goal

Provide a first-class distribution package and integration path for OpenClaw (小龍蝦) / ClawHub users, enabling athletes to use Long Run Hybrid Coach directly from their preferred chat interfaces (e.g. WeChat, Telegram, Discord, desktop GUI) via hosted MCP or local MCP.

This realizes Priority 3 (P3) of the distribution roadmap (#114) and builds upon the canonical Skill + MCP foundation (#117).

## Context & Motivation

OpenClaw is an open-source conversational AI agent framework. Users ("

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/133) · 2026-08-17 · open · 3 comments

### [Direction settled] Hosted + local distribution architecture; current status lives in #15/docs/distribution

## 目的

把 Long Run Hybrid Coach 的對外分發收斂成一條清楚的產品路線，避免之後每遇到一個 agent 平台就重新討論架構或複製一套 coach。

已拍板：

- **Hosted MCP 會長期存在，且是 canonical public service。** 不再討論要不要移除 hosted backend。
- **Local execution 也是 first-class alternative。** 使用者可以選擇完全在自己電腦執行，不依賴我們的 hosted backend。
- 所有平台入口都只能是薄 adapter；不得複製 Coach 邏輯、PlanState schema、validator、delivery semantics 或 provider adapter。
- 模型推理留在使用者選擇的 ChatGPT / Claude / Gemini / OpenClaw 等 agent；本產品不增加自己的 LLM API runtime。

## 兩種 canonical deployment mode

### A. Hosted

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/114) · 2026-08-16 · closed · 3 comments

### Feasibility spike: distribute Long Run Hybrid Coach through Claude Connectors (Directory + self-deploy)

## Owner request

先驗證 Long Run Hybrid Coach 能不能透過 Claude 的連接器機制對外分發，讓更多人（不只 ting 自己）能自助用起來——包含官方 Connectors Directory 上架，以及讓技術使用者自架自己那份的路徑。本 issue 只做查證與方案比較，不寫 production 程式碼、不改 gateway。

## Why this is a separate spike

現有產品已經有：

- 一個 stable HTTPS Coach Gateway，OAuth-authenticated，owner-scoped PlanState；
- Custom GPT Action 路徑（OpenAPI/REST，不是 MCP 協定）；
- #35 Part B 定義了「把既有 command surface 包成 MCP tools」的範圍，但明確被 owner 在 2026-08-15 的 comment 標記為 deferred（"Part B (hosted MCP) remains deferred; do no

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/98) · 2026-08-16 · closed · 3 comments

### startCoachSession returns 63 KB, and it grows with the athlete's history

## Symptom

Every coaching turn opens with `startCoachSession`. Measured against the owner account on 2026-08-23, one call returns **63,560 characters** of JSON:

| part | chars |
|---|---|
| `context` | 41,690 |
| `plan_state` | 10,285 |
| `coaching_guidance` | 7,600 |
| `delivery` | 2,791 |
| `reconciliation` | 903 |
| rest | ~290 |

Inside `context`:

| field | chars |
|---|---|
| `segment_execution` | 16,206 |
| `recent_actuals` | 8,271 |
| `cycle_sessions` | 3,048 |
| `baseline_evidence` | 

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/233) · 2026-08-23 · closed · 2 comments

### The product verifies Intervals against the live account and never verified itself against a live client

## What was found

This repository has a rule it applies rigorously — *"Verify against the live account, not against the plan"* — and had never applied it to its own clients. Over 1,600 tests, every one of them checking the repo against the repo. Nothing asked whether an athlete's ChatGPT or claude.ai actually receives what the server sends.

Asking produced this:

| Client | Has the orchestration layer before the first turn? | Evidence |
| --- | --- | --- |
| **claude.ai (web connector)** | **N

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/186) · 2026-08-19 · open · 2 comments

### Restrict MCP client registration to trusted redirect origins and retain security audit events

## Threat

`/oauth/authorize` now binds a presented `client_id` to the redirect URIs sealed into that registration, so one client's id cannot be paired with another callback. That is necessary but not sufficient.

`/oauth/register` is still open to any caller. Today an attacker can register `https://evil.example/callback`, receive a valid client id, start PKCE themselves, induce an athlete to approve the real Long Run Hybrid Coach application at Intervals, and receive this gateway's authorizatio

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/115) · 2026-08-16 · closed · 2 comments

## Most recent

### [Current — eval coverage] Give pre_plan_observations a contract so a first-plan case can name its evidence

## Finding

The first plan is the only layer that may author goal, cycle, week and outlook at
once, and it is the only layer with no committed eval case. That is not because the
read is missing — two scenarios already take it and pass:

- `09_no_plan__provider_healthy`
- `10_no_plan__recovery_read_fails`

Both hand back `pre_plan_observations` carrying `athlete_evidence`,
`recent_training` and `recovery_signals`. No eval case uses either. The two
committed `plan_cycle` cases both bind to `12_pla

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/314) · 2026-08-29 · closed · 0 comments

### plan_state_exists tells the model what exists but not what the retry needs, so the turn ends there

## What happened, with the counter as evidence

On 2026-08-27 an external athlete's client made four tool calls in one day — one each of
`startCoachSession`, `inspectIntervalsPermissions`, `prepareCoachDecision` and
`confirmPrescribedStrength`. Two were accepted, two refused: `plan_state_exists` and
`invalid_request`. The account already had a plan from the day before and ended the day
with **zero deliveries**. No `applyCoachDecision`, no delivery call of any kind.

`plan_state_exists` can only 

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/303) · 2026-08-28 · closed · 0 comments

### An anonymous OAuth 400 says only "400": the access log the silent refusals defer to carries no error code

## What happened

On 2026-08-27, a local MCP client repeatedly failed to connect and I could not
tell from the logs why. The same sequence appears twice, ~20 minutes apart, from
two different loopback ports:

```
02:37:52.470  POST /oauth/register  -> 201   client_registration accepted (127.0.0.1:9999)
02:37:52.784  GET  /oauth/authorize -> 400   no security event
02:37:53.061  GET  /oauth/authorize -> 400   no security event
02:37:53.461  POST /oauth/register  -> 400   client_registration refus

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/301) · 2026-08-27 · closed · 1 comment

### [Edge case] The DNS-rebinding check still trusts a header outside release mode

Noticed while doing #288 item 2, and deliberately not fixed there. **Low priority, and the cheap fix is probably to change nothing** — filed so the next reader of `_require_allowed_origin` does not have to re-derive it.

## What

`_require_allowed_origin` (`garmin_coach_loop/gateway.py`) refuses a browser calling `/mcp` from an origin this server does not answer to. Three answers are trusted: `MCP_ALLOWED_ORIGINS`, the operator's `allowed_mcp_origins`, and **this request's own origin**, computed

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/297) · 2026-08-27 · open · 0 comments

### [Scale gate — availability] Bound hosted gateway concurrency and request timeouts

## Problem

The production hosted MCP endpoint is internet-facing and is served directly by Python's `http.server.ThreadingHTTPServer` in `garmin_coach_loop/gateway.py`. The gateway already bounds request bodies (`MAX_REQUEST_BYTES`) and outbound provider calls have timeouts, but the public inbound server currently has no explicit application-level concurrency ceiling or per-connection/read timeout.

`ThreadingHTTPServer` creates a thread per request/connection. A sufficiently large number of sl

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/285) · 2026-08-26 · open · 1 comment

### [Release gate — OAuth] Enforce single-use authorization codes

## Problem

The hosted MCP OAuth bridge issues its own authorization code as a signed/encrypted stateless envelope in `garmin_coach_loop/gateway.py`. The code is short-lived (`AUTHORIZATION_CODE_TTL_SECONDS = 60`) and is bound to the client, redirect URI and PKCE challenge, but it is not consumed after a successful token exchange.

**Correction from the 2026-08-26 security review (#288): `resource` is not currently bound consistently.** Authorization can seal no resource, and token exchange comp

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/284) · 2026-08-26 · closed · 1 comment

### [Release gate — supply chain] Pin and verify MCP Registry publisher

## Problem

`.github/workflows/publish-mcp-registry.yml` downloads `mcp-publisher` from the mutable `releases/latest` URL and executes it directly in a job with `id-token: write`:

```yaml
curl -fsSL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_...tar.gz" | tar xz mcp-publisher
./mcp-publisher login github-oidc
./mcp-publisher publish
```

There is no exact publisher version, checksum/signature verification, or immutable artifact pin. If the upstream r

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/283) · 2026-08-26 · open · 0 comments

### [Release gate — privacy] Sanitize filesystem failures before model-facing output

## Finding

Hosted failure paths are not consistently sanitized before they become client/model-visible text.

Two confirmed paths on current main:

### 1. Common StateStoreError mapping can echo an absolute path

`store._read_object(path)` catches an `OSError` and builds:

```python
raise StateStoreError(f"cannot read {path.name}: {exc}")
```

Using `path.name` in the prefix looks safe, but the original `OSError` string commonly carries the full path itself, e.g. a `PermissionError` is shaped l

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/282) · 2026-08-26 · closed · 0 comments

### [Scheduled — verify in onboarding] First-plan preview/apply plan_id handoff

## Finding

The unified `prepareCoachDecision` / `applyCoachDecision` first-plan path has a self-contradictory handoff.

When no PlanState exists, `prepare_decision()` translates to `prepare_initialization()`, whose response contains the **derived but not yet persisted** identifiers:

```python
"plan_id": plan["plan_id"],
"plan_version": plan["version"],
```

The public MCP output schema allows those values, so the model receives them.

But the matching apply path translates through `_first_plan

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/280) · 2026-08-26 · open · 1 comment

### This gateway speaks 2025-06-18, and two newer MCP revisions now exist

## What is true today

`PROTOCOL_VERSION` in [`garmin_coach_loop/mcp_transport.py`](../blob/main/garmin_coach_loop/mcp_transport.py)
is `2025-06-18`, and `HTTP_PROTOCOL_VERSIONS` is that plus `2025-03-26`. A
`MCP-Protocol-Version` header outside those two is a `400` before the body is read.

The specification repository now carries five released revisions: `2024-11-05`,
`2025-03-26`, `2025-06-18`, **`2025-11-25`** and **`2026-07-28`**. We are two behind.

## Why this is not obviously broken, and

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/202) · 2026-08-20 · open · 0 comments

### Walk a real Hermes Agent connection once, and record what it does

## Why this is blocking something

Two separate rules point at the same missing fact:

- **Hermes Atlas** — the community map — requires an entry to be *"specifically built for or
  integrated with Hermes Agent"*. This is a general MCP server that has never been connected
  to one, so it does not qualify today.
- **Our own checklist** — step 2 of [`docs/distribution/hermes-agent.md`](../blob/main/docs/distribution/hermes-agent.md)
  says to connect once from a real client *before submitting anyt

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/201) · 2026-08-20 · open · 0 comments

### recovery_signals makes a client send nine explicit nulls to report one reading

## What

`startCoachSession.recovery_signals` requires **all ten** per-day fields on every day it carries — `date`, `readiness_score`, `readiness_level`, `hrv_status`, `hrv_7d_avg_ms`, `acute_load`, `recovery_time_sec`, `body_battery_high`, `body_battery_low`, `avg_stress`. Both the MCP input schema in `garmin_coach_loop/mcp_transport.py` and `_keys` inside `validation.py` demand the full set.

A local agent that can read only Body Battery therefore has to send nine explicit `null`s per day to r

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/187) · 2026-08-19 · closed · 1 comment

### The plugin token endpoint redeems a code for anyone: add gateway client authentication to /oauth/intervals/token

Found during a full security review of the gateway on 2026-08-19. Nothing else in that review met the reporting bar; this one is a residual risk worth closing rather than a proven vulnerability, and the prerequisite is stated below.

## What

`CoachGateway.exchange_token` (`garmin_coach_loop/gateway.py`, the handler behind `POST /oauth/intervals/token`) reads exactly two fields from the form: `grant_type` and `code`. It then calls `_redeem_intervals_code`, which posts the server-held `GARMIN_COA

[Read the thread](https://github.com/atomchung/long-run-hybrid-coach/issues/183) · 2026-08-19 · closed · 0 comments

The remaining reports are on [the project's issue tracker](https://github.com/atomchung/long-run-hybrid-coach/issues).
