A merged PR may require knowledge-base updates. Review the change and update library/ as needed.
Source PR
- PR: #300 - fix: Portkey fail-closed (no empty model), honest gateway status, extraction error visibility
- Author: @thenotoriousllama
- Merged into:
main
- Files changed: 20
PR description
Fixes ISS-005 ("fail closed, report honestly"): a fresh install sent model: "" to Portkey 373 times, every extraction job was swallowed as done, and /health said unreachable for what was actually a 401.
What changed
1. Vault settings validation (src/daemon/runtime/vault/api.ts)
POST /api/settings/portkey.enabled with true now 400s unless a non-empty activeModel is stored (set a non-empty activeModel before enabling portkey.enabled).
- Clearing
activeModel (empty/whitespace write) 400s while portkey.enabled is true, naming the gateway; an empty activeModel is now never storable regardless of gateway state.
2. Fail-closed selection + typed no_model health state (assemble.ts, health.ts)
readPortkeySelection returns a typed "no_model" sentinel when the gateway is on but activeModel is missing/empty — NO Portkey target is built (no chat transport, no rerank seam), so the daemon can never route model: "".
PortkeyHealth gains the additive "no_model" literal; /health reasons.portkey reports it instead of a false ok/off.
- Final transport guard (
transport-portkey.ts): an empty model throws a typed ProviderError(400) without a network call and without firing the unreachable signal (a config error is not gateway unreachability).
3. unreachable keeps the HTTP status (assemble.ts, health.ts)
recordPortkeyUnreachable now caches the observed status code; /health carries the additive reasons.portkeyUnreachableStatus (401 bad key vs 400 bad request vs 503 network) beside the unchanged "unreachable" literal. Hive's dashboard chip matches r.portkey === "unconfigured" || r.portkey === "unreachable" (hive/src/dashboard/web/pages/dashboard.tsx:123) — both literals are preserved, so this is strictly additive. (Follow-up for hive: render no_model as degraded and show the status code.)
**4. Extraction failure visibility (pipeline/memory-formation.ts, pipeline/extraction.ts p
Files touched
.claude-plugin/marketplace.json (+2/-2)
.claude-plugin/plugin.json (+1/-1)
CHANGELOG.md (+4/-0)
harnesses/claude-code/.claude-plugin/plugin.json (+1/-1)
harnesses/codex/package.json (+1/-1)
harnesses/openclaw/openclaw.plugin.json (+1/-1)
harnesses/openclaw/package.json (+1/-1)
package-lock.json (+2/-2)
package.json (+1/-1)
src/daemon/runtime/assemble.ts (+46/-7)
src/daemon/runtime/health.ts (+41/-3)
src/daemon/runtime/inference/transport-portkey.ts (+9/-0)
src/daemon/runtime/pipeline/index.ts (+2/-0)
src/daemon/runtime/pipeline/memory-formation.ts (+69/-0)
src/daemon/runtime/vault/api.ts (+25/-1)
tests/daemon/runtime/assemble-portkey-selection.test.ts (+78/-0)
tests/daemon/runtime/health.test.ts (+76/-3)
tests/daemon/runtime/inference/transport-portkey.test.ts (+57/-0)
tests/daemon/runtime/pipeline/memory-formation.test.ts (+63/-4)
tests/daemon/runtime/vault/settings-api.test.ts (+94/-0)
Auto-generated by .github/workflows/kb-issue-on-merge.yaml on merge.
A merged PR may require knowledge-base updates. Review the change and update
library/as needed.Source PR
mainPR description
Fixes ISS-005 ("fail closed, report honestly"): a fresh install sent
model: ""to Portkey 373 times, every extraction job was swallowed as done, and/healthsaidunreachablefor what was actually a 401.What changed
1. Vault settings validation (
src/daemon/runtime/vault/api.ts)POST /api/settings/portkey.enabledwithtruenow 400s unless a non-emptyactiveModelis stored (set a non-empty activeModel before enabling portkey.enabled).activeModel(empty/whitespace write) 400s whileportkey.enabledis true, naming the gateway; an emptyactiveModelis now never storable regardless of gateway state.2. Fail-closed selection + typed
no_modelhealth state (assemble.ts,health.ts)readPortkeySelectionreturns a typed"no_model"sentinel when the gateway is on butactiveModelis missing/empty — NO Portkey target is built (no chat transport, no rerank seam), so the daemon can never routemodel: "".PortkeyHealthgains the additive"no_model"literal;/healthreasons.portkeyreports it instead of a falseok/off.transport-portkey.ts): an empty model throws a typedProviderError(400)without a network call and without firing the unreachable signal (a config error is not gateway unreachability).3.
unreachablekeeps the HTTP status (assemble.ts,health.ts)recordPortkeyUnreachablenow caches the observed status code;/healthcarries the additivereasons.portkeyUnreachableStatus(401 bad key vs 400 bad request vs 503 network) beside the unchanged"unreachable"literal. Hive's dashboard chip matchesr.portkey === "unconfigured" || r.portkey === "unreachable"(hive/src/dashboard/web/pages/dashboard.tsx:123) — both literals are preserved, so this is strictly additive. (Follow-up for hive: renderno_modelas degraded and show the status code.)**4. Extraction failure visibility (
pipeline/memory-formation.ts,pipeline/extraction.tspFiles touched
.claude-plugin/marketplace.json(+2/-2).claude-plugin/plugin.json(+1/-1)CHANGELOG.md(+4/-0)harnesses/claude-code/.claude-plugin/plugin.json(+1/-1)harnesses/codex/package.json(+1/-1)harnesses/openclaw/openclaw.plugin.json(+1/-1)harnesses/openclaw/package.json(+1/-1)package-lock.json(+2/-2)package.json(+1/-1)src/daemon/runtime/assemble.ts(+46/-7)src/daemon/runtime/health.ts(+41/-3)src/daemon/runtime/inference/transport-portkey.ts(+9/-0)src/daemon/runtime/pipeline/index.ts(+2/-0)src/daemon/runtime/pipeline/memory-formation.ts(+69/-0)src/daemon/runtime/vault/api.ts(+25/-1)tests/daemon/runtime/assemble-portkey-selection.test.ts(+78/-0)tests/daemon/runtime/health.test.ts(+76/-3)tests/daemon/runtime/inference/transport-portkey.test.ts(+57/-0)tests/daemon/runtime/pipeline/memory-formation.test.ts(+63/-4)tests/daemon/runtime/vault/settings-api.test.ts(+94/-0)Auto-generated by
.github/workflows/kb-issue-on-merge.yamlon merge.