Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion codec_chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@
</style>
</head>
<body>
<script src="/static/codec-shell.js?v=42b8fd6a" data-page="chat" data-title="Chat" data-status data-model-slot></script>
<script src="/static/codec-shell.js?v=611c650d" data-page="chat" data-title="Chat" data-status data-model-slot></script>
<div class="app cs-main" id="appContainer">
<div class="messages" id="messages" role="log" aria-live="polite" aria-label="Conversation"></div>

Expand Down
2 changes: 1 addition & 1 deletion codec_cortex.html
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@
</style>
</head>
<body>
<script src="/static/codec-shell.js?v=42b8fd6a" data-page="cortex" data-title="Cortex"></script>
<script src="/static/codec-shell.js?v=611c650d" data-page="cortex" data-title="Cortex"></script>
<div class="cs-main cortex-main">
<div id="stats"></div>
<div id="viewSwitch">
Expand Down
2 changes: 1 addition & 1 deletion codec_dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@
</style>
</head>
<body>
<script src="/static/codec-shell.js?v=42b8fd6a" data-page="home" data-title="Today" data-status></script>
<script src="/static/codec-shell.js?v=611c650d" data-page="home" data-title="Today" data-status></script>

<!-- ── Connect your AI (first run) ─────────────────────────────────────────
Shown until CODEC has a VERIFIED brain. A fresh paid install had no way
Expand Down
2 changes: 1 addition & 1 deletion codec_tasks.html
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@
</style>
</head>
<body>
<script src="/static/codec-shell.js?v=42b8fd6a" data-page="tasks" data-title="Tasks" data-status></script>
<script src="/static/codec-shell.js?v=611c650d" data-page="tasks" data-title="Tasks" data-status></script>
<div class="cs-main cs-scroll">
<div class="container">
<div class="tabs" role="tablist" aria-label="Tasks sections">
Expand Down
2 changes: 1 addition & 1 deletion codec_vibe.html
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@
</style>
</head>
<body>
<script src="/static/codec-shell.js?v=42b8fd6a" data-page="vibe" data-title="Vibe" data-status></script>
<script src="/static/codec-shell.js?v=611c650d" data-page="vibe" data-title="Vibe" data-status></script>
<div class="ov" id="ov" onclick="cS()"></div>
<div class="sd" id="sd" role="dialog" aria-modal="true" aria-label="Projects" data-dialog="sh">
<div style="padding:14px 16px;border-bottom:1px solid var(--bd);display:flex;align-items:center;justify-content:space-between"><h2 style="font-family:var(--mn);font-size:var(--fs-13);color:var(--ac)">PROJECTS</h2><button type="button" onclick="cS()" aria-label="Close projects" data-dialog-close style="background:0;border:0;color:var(--tx2);cursor:pointer;display:flex;padding:0"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg></button></div>
Expand Down
2 changes: 1 addition & 1 deletion codec_voice.html
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@
</style>
</head>
<body id="body">
<script src="/static/codec-shell.js?v=42b8fd6a" data-page="voice" data-title="Voice" data-status></script>
<script src="/static/codec-shell.js?v=611c650d" data-page="voice" data-title="Voice" data-status></script>
<div class="call-container cs-main">

<!-- The orb: its size follows the level. Press and hold it to talk. -->
Expand Down
2 changes: 1 addition & 1 deletion docs/known-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ same problem and was fixed on 2026-09-05 (entry above). Fix: send these through
`codec_audit.log_event` like the other services. Found in the Mac merge pass of
#418-#439. Revisit: the next item that touches either bridge.

## Inbox actions show "HTTP 400" instead of the reason (2026-10-07)
## Inbox actions show "HTTP 400" instead of the reason (2026-10-07) — FIXED (the shell reads {error} or {detail})

When an Inbox action is refused (for example a grant that is not allowed), the
toast says "HTTP 400" and drops the server's `error` / `detail` text, so the
Expand Down
11 changes: 9 additions & 2 deletions static/codec-shell.js
Original file line number Diff line number Diff line change
Expand Up @@ -1246,11 +1246,18 @@
if (m) h['x-csrf-token'] = m[1];
return h;
}
// The server's reason for a refusal: {error} from CODEC's own routes, {detail} from
// FastAPI's HTTPException (a list of {msg} for a validation error); else the status.
function errText(d, status) {
var x = d && (d.error || d.detail);
if (Array.isArray(x)) x = x.map(function (e) { return (e && e.msg) || ''; }).filter(Boolean).join('; ');
return (typeof x === 'string' && x) ? x : 'HTTP ' + status;
}
function postJSON(url, data) {
var h = csrf({ 'Content-Type': 'application/json' });
return fetch(url, { method: 'POST', headers: h, body: JSON.stringify(data || {}) }).then(function (r) {
return r.json().catch(function () { return {}; }).then(function (d) {
if (!r.ok) throw new Error(d.error || ('HTTP ' + r.status));
if (!r.ok) throw new Error(errText(d, r.status));
return d;
});
});
Expand Down Expand Up @@ -1729,7 +1736,7 @@
if (!SAY.reqs[i]) {
SAY.reqs[i] = fetch('/api/tts', { method: 'POST', headers: csrf({ 'Content-Type': 'application/json' }),
body: JSON.stringify({ text: SAY.parts[i] }) }).then(function (r) {
if (!r.ok) return r.json().catch(function () { return {}; }).then(function (d) { throw new Error(d.error || ('HTTP ' + r.status)); });
if (!r.ok) return r.json().catch(function () { return {}; }).then(function (d) { throw new Error(errText(d, r.status)); });
return r.blob();
}).then(function (b) {
var u = URL.createObjectURL(b);
Expand Down
37 changes: 37 additions & 0 deletions tests/test_shell_error_reason.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""A refused page action shows the server's reason, not "HTTP 400" (docs/known-issues.md).

The shell's postJSON only read {error}; FastAPI's HTTPException answers {detail},
so an Inbox Grant or Resume refused by the agents routes said "HTTP 400".
"""
from __future__ import annotations

import json
import re
import shutil
import subprocess
from pathlib import Path

import pytest

SHELL = (Path(__file__).resolve().parent.parent / "static" / "codec-shell.js").read_text(encoding="utf-8")


def _err_text_source() -> str:
m = re.search(r"function errText\(d, status\) \{.*?\n \}\n", SHELL, re.S)
assert m, "errText is defined in the shell"
return m.group(0)


def test_every_shell_request_helper_uses_the_reason():
assert "throw new Error(d.error || ('HTTP ' + r.status))" not in SHELL
assert SHELL.count("errText(d, r.status)") >= 2


@pytest.mark.skipif(shutil.which("node") is None, reason="needs node")
def test_the_reason_comes_from_error_detail_or_a_validation_list():
cases = [({"error": "Not allowed here."}, 400), ({"detail": "agent must be paused"}, 409),
({"detail": [{"msg": "field required"}, {"msg": "bad value"}]}, 422), ({}, 500), (None, 502)]
js = _err_text_source() + "console.log(JSON.stringify(%s.map(c => errText(c[0], c[1]))));" % json.dumps(cases)
out = subprocess.run(["node", "-e", js], capture_output=True, text=True, timeout=30, check=True).stdout
assert json.loads(out) == ["Not allowed here.", "agent must be paused", "field required; bad value",
"HTTP 500", "HTTP 502"]
Loading