Treat inference hook denials as terminal errors (#60511)

## Why

An explicit denial from a workspace inference hook should stop the request, including retries and compaction model fallback.

## What changed

Recognize `inference_hook_denied` provider errors and preserve the provider's message, with a fallback when none is supplied. Expose the distinct `inferenceHookDenied` error through the app-server protocol and generated schemas and SDK types. Treat the denial as non-retryable during turns, compaction, and guardian review.

## Testing

Add coverage for terminal denials over HTTP, SSE, and WebSocket, error serialization and persistence in `thread/read`, and compaction failures that preserve token usage and history without retrying or falling back to another model.

GitOrigin-RevId: 2ebd480f01e52f4e6bd3db8a0f92d5814d18ccb1
This commit is contained in:
ccui-oai
2026-11-10 03:21:26 +00:00
committed by copyberry
parent 1099fe0694
commit 0811df3976
43 changed files with 292 additions and 51 deletions
@@ -83,6 +83,8 @@ pub enum CodexErrorInfo {
ModelNotFound,
// Capacity is unavailable on a route restricted by server policy.
PolicyCapacityUnavailable,
/// The workspace's inference hook explicitly denied this request.
InferenceHookDenied,
CyberPolicy,
MisalignmentPolicyViolation,
TooManyDenials,
@@ -163,6 +165,7 @@ impl From<CoreCodexErrorInfo> for CodexErrorInfo {
CodexErrorInfo::PolicyCapacityUnavailable
}
CoreCodexErrorInfo::CyberPolicy => CodexErrorInfo::CyberPolicy,
CoreCodexErrorInfo::InferenceHookDenied => CodexErrorInfo::InferenceHookDenied,
CoreCodexErrorInfo::BioPolicy => CodexErrorInfo::Other,
CoreCodexErrorInfo::MisalignmentPolicyViolation => {
CodexErrorInfo::MisalignmentPolicyViolation
@@ -4907,6 +4907,10 @@ fn core_error_info_converts_to_camel_case() {
json!("policyCapacityUnavailable"),
),
(CoreCodexErrorInfo::CyberPolicy, json!("cyberPolicy")),
(
CoreCodexErrorInfo::InferenceHookDenied,
json!("inferenceHookDenied"),
),
(CoreCodexErrorInfo::BioPolicy, json!("other")),
(CoreCodexErrorInfo::InvalidPrompt, json!("other")),
(
@@ -4918,9 +4922,11 @@ fn core_error_info_converts_to_camel_case() {
json!("rateLimitExceeded"),
),
] {
let converted = CodexErrorInfo::from(core);
assert_eq!(serde_json::to_value(&converted).unwrap(), expected);
assert_eq!(
serde_json::to_value(CodexErrorInfo::from(core)).unwrap(),
expected
serde_json::from_value::<CodexErrorInfo>(expected).unwrap(),
converted
);
}
}