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
- Set/copy the current Observation TV token into the client and resend the request with the token header
- If the token was rotated, re-pair the TV client with the new token from worker settings
- Verify the client sends the header on every request (check that a reverse proxy is not stripping Authorization)
- 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
- Load the TV token from config at startup and fail fast if absent
- Send the Authorization header via a shared fetch wrapper so no call forgets it
- Re-fetch the token after any re-pairing/rotation instead of caching indefinitely
- Never proxy TV requests through layers that strip Authorization headers
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)