fix(#2351): scan error result raw so prompt-too-long in result field is detected
This commit is contained in:
parent
4d76305f2f
commit
9aaf8b93fb
1 changed files with 43 additions and 12 deletions
|
|
@ -75,18 +75,29 @@ impl Sentinels {
|
||||||
if matches!(ty, Some("assistant" | "user")) {
|
if matches!(ty, Some("assistant" | "user")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// The terminal `result` event carries the model's final answer in its
|
// A `result` event's `result` field is the model's final answer on a
|
||||||
// `result` field — model-authored text, same trust level as an
|
// SUCCESSFUL turn — model-authored text, same trust level as an
|
||||||
// assistant message. A marker quoted there (e.g. the model explaining
|
// assistant message. A marker quoted there (the model explaining *this*
|
||||||
// *this* code, or echoing an error string back) would falsely trip a
|
// code, or echoing an error string back) would falsely trip a sentinel
|
||||||
// sentinel and needlessly kill/downgrade the turn. So for a `result`
|
// and needlessly kill/downgrade the turn, so for a *success* result we
|
||||||
// event, scan the control fields (`subtype` / `error` / `is_error`)
|
// scrub `result` before scanning and rely on the control fields.
|
||||||
// but scrub the model-authored `result` field first; a genuine
|
//
|
||||||
// prompt-too-long/auth/session signal lives in those control fields
|
// But on an ERROR result the same `result` field carries claude-code's
|
||||||
// (it's emitted *instead of* a successful model answer), never in the
|
// OWN failure text — a genuine "Prompt is too long" (an API 400) lands
|
||||||
// `result` text. Other control events (`error` / `system`) are
|
// there, emitted *instead of* a model answer. The model can't forge
|
||||||
// claude-authored end to end, so they scan raw.
|
// `is_error` / a non-`success` subtype (the CLI sets them from the real
|
||||||
if ty == Some("result") {
|
// outcome), so an error result is claude-authored end to end and is
|
||||||
|
// safe to scan raw. Scrubbing it unconditionally (the original
|
||||||
|
// result-scrub fix) blinded prompt-too-long / auth detection — hence
|
||||||
|
// keying the scrub on success only. Other control events are
|
||||||
|
// claude-authored throughout, so they also scan raw.
|
||||||
|
let error_result = ty == Some("result")
|
||||||
|
&& (event.get("is_error").and_then(serde_json::Value::as_bool) == Some(true)
|
||||||
|
|| event
|
||||||
|
.get("subtype")
|
||||||
|
.and_then(serde_json::Value::as_str)
|
||||||
|
.is_some_and(|s| s != "success"));
|
||||||
|
if ty == Some("result") && !error_result {
|
||||||
let mut scrubbed = event.clone();
|
let mut scrubbed = event.clone();
|
||||||
if let Some(obj) = scrubbed.as_object_mut() {
|
if let Some(obj) = scrubbed.as_object_mut() {
|
||||||
obj.remove("result");
|
obj.remove("result");
|
||||||
|
|
@ -164,6 +175,26 @@ mod tests {
|
||||||
assert!(matches!(s.soft_error(), Some(Error::PromptTooLong)));
|
assert!(matches!(s.soft_error(), Some(Error::PromptTooLong)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prompt_too_long_in_error_result_field_is_detected() {
|
||||||
|
// Regression guard: claude-code reports a genuine "Prompt is too long"
|
||||||
|
// (API 400) in the `result` field of an ERROR result event. The earlier
|
||||||
|
// result-scrub blinded this by stripping `result` unconditionally. An
|
||||||
|
// error result is claude-authored (the model can't forge `is_error`),
|
||||||
|
// so it must be scanned raw.
|
||||||
|
for raw in [
|
||||||
|
r#"{"type":"result","subtype":"error_during_execution","is_error":true,"result":"API Error: 400 Prompt is too long"}"#,
|
||||||
|
r#"{"type":"result","subtype":"success","is_error":true,"result":"Prompt is too long"}"#,
|
||||||
|
] {
|
||||||
|
let s = Sentinels::default();
|
||||||
|
s.scan_stdout_json(&json(raw), raw);
|
||||||
|
assert!(
|
||||||
|
matches!(s.soft_error(), Some(Error::PromptTooLong)),
|
||||||
|
"prompt-too-long in an error result's `result` field must be detected: {raw}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn result_field_model_text_quoting_marker_is_ignored() {
|
fn result_field_model_text_quoting_marker_is_ignored() {
|
||||||
// The terminal `result` event's `result` field is the model's final
|
// The terminal `result` event's `result` field is the model's final
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue