thedotmack/claude-mem · error

Unauthorized

Unauthorized

Error message

Missing or invalid Observation TV token

What it means

createRemoteReadOnlyGuard protects the Observation TV remote-access endpoints. When a request lacks a valid TV token and is not an allowed read-only request, the guard rejects it with 401 'Missing or invalid Observation TV token'. The token must be supplied (and match the configured Observation TV token) for remote access.

Solutions

  1. Set/copy the current Observation TV token into the client and resend the request with the token header
  2. If the token was rotated, re-pair the TV client with the new token from worker settings
  3. Verify the client sends the header on every request (check that a reverse proxy is not stripping Authorization)
  4. Confirm the endpoint is the remote (read-only) route and you are not hitting it from a context that should use full local access

Example fix

// before
fetch('http://worker:37777/api/tv/state');
// after
fetch('http://worker:37777/api/tv/state', {
  headers: { Authorization: `Bearer ${tvToken}` }
});
Defensive patterns

Strategy: validation

Validate before calling

const token = process.env.OBSERVATION_TV_TOKEN;
if (!token) throw new Error('Observation TV token not configured; cannot call remote TV endpoints');

Type guard

function hasTvToken(h: Record<string,string>): h is Record<string,string> & { Authorization: string } {
  return typeof h.Authorization === 'string' && h.Authorization.length > 0;
}

Try / catch

try {
  const res = await fetch(url, { headers: { Authorization: `Bearer ${tvToken}` } });
  if (res.status === 401) throw new UnauthorizedTvError(await res.text());
} catch (e) {
  if (e instanceof UnauthorizedTvError) { /* refresh token / re-pair */ }
  throw e;
}

Prevention

When it happens

Trigger: Calling an Observation TV remote endpoint without an Authorization/token header, with a malformed token, or with a token that does not match the configured value; the guard's decision path falls into the non-403 else branch (status 401).

Common situations: TV display client not configured with the pairing token; token rotated or regenerated on the server while the client still caches the old one; proxy stripping the Authorization header; typos when copying the token from settings.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/1194d7f2a06c126d. Report an issue: GitHub.

Appendix: source

Thrown at src/services/worker/http/middleware.ts:310

      // query string in Express, which is what makes that safe.
      logRemoteDenial({
        path: req.path,
        method: req.method,
        clientIp,
        reason: decision.reason,
      });
      // Always write the response. The worker never calls finalizeRoutes(), so
      // it has no terminal error handler — forwarding an error to Express
      // would land in its default handler and return an HTML stack page.
      if (decision.status === 404) {
        res.status(404).json({ error: 'Not found' });
      } else if (decision.status === 403) {
        res.status(403).json({
          error: 'Forbidden',
          message: 'Observation TV remote access is read-only'
        });
      } else {
        res.status(401).json({
          error: 'Unauthorized',
          message: 'Missing or invalid Observation TV token'
        });
      }
      return;
    }

    // 6. Pass.
    res.setHeader('Cache-Control', 'no-store');
    next();
  };
}

export function summarizeRequestBody(method: string, path: string, body: any): string {
  if (!body || Object.keys(body).length === 0) return '';

  if (path.includes('/init')) {
    return '';

View on GitHub (pinned to d8bc9755e7)