From 42e8ac9d6f2b48d6b8a8e81e30e46c0edab9dd70 Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 27 Aug 2026 12:11:33 -0500 Subject: [PATCH] fix(runner): restore remote session checkpoints --- .../crates/runner-core/src/durable/runner.rs | 16 +++++++++ .../crates/runner-core/src/durable/state.rs | 35 +++++++++++++++++-- .../crates/runner-core/src/provider_events.rs | 6 +++- .../native-session-executor.test.ts | 14 ++++++++ .../native-runtime/native-session-executor.ts | 3 +- 5 files changed, 69 insertions(+), 5 deletions(-) diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/durable/runner.rs b/packages/paperclip-runner/runner/crates/runner-core/src/durable/runner.rs index 40397e0598c..52a45dd0623 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/durable/runner.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/durable/runner.rs @@ -2609,6 +2609,22 @@ mod tests { ); } + #[test] + fn bearer_values_are_redacted_without_erasing_safe_failure_context() { + let sanitized = crate::durable::redact_text( + "MCP request failed: Authorization: Bearer secret-token, status 401 Unauthorized", + ); + assert_eq!( + sanitized, + "MCP request failed: Authorization: Bearer [REDACTED], status 401 Unauthorized" + ); + assert!(!sanitized.contains("secret-token")); + assert_eq!( + crate::durable::redact_text("Bearer first; retry used bearer second\nfailed"), + "Bearer [REDACTED]; retry used bearer [REDACTED]\nfailed" + ); + } + #[test] fn durable_events_strip_binary_payloads_but_preserve_artifact_metadata() { let sanitized = crate::durable::sanitize_value(&json!({ diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs b/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs index 6b11689d161..2e452cc914c 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs @@ -758,10 +758,39 @@ fn redaction_key(key: &str) -> bool { } pub fn redact_text(input: &str) -> String { - if input.to_ascii_lowercase().contains("bearer ") { - return "[REDACTED]".to_owned(); + const BEARER_PREFIX: &[u8] = b"bearer "; + let bytes = input.as_bytes(); + let mut output = String::with_capacity(input.len()); + let mut cursor = 0; + while cursor + BEARER_PREFIX.len() <= bytes.len() { + let Some(relative_start) = bytes[cursor..] + .windows(BEARER_PREFIX.len()) + .position(|candidate| candidate.eq_ignore_ascii_case(BEARER_PREFIX)) + else { + break; + }; + let start = cursor + relative_start; + let value_start = start + BEARER_PREFIX.len(); + output.push_str(&input[cursor..start]); + output.push_str(&input[start..value_start]); + output.push_str("[REDACTED]"); + + let value_end = input[value_start..] + .char_indices() + .find_map(|(offset, character)| { + (character.is_whitespace() + || matches!(character, '\"' | '\'' | ',' | ';' | ')' | ']' | '}')) + .then_some(value_start + offset) + }) + .unwrap_or(input.len()); + cursor = value_end; + } + if output.is_empty() { + input.to_owned() + } else { + output.push_str(&input[cursor..]); + output } - input.to_owned() } pub fn sanitize_value(value: &Value) -> Value { diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/provider_events.rs b/packages/paperclip-runner/runner/crates/runner-core/src/provider_events.rs index e2e68fedaa3..5f3fc220af2 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/provider_events.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/provider_events.rs @@ -586,7 +586,11 @@ mod tests { &json!({"item":{"id":"exec-1","type":"commandExecution","status":"completed","aggregatedOutput":output}}), ); let payload = &events[0].1; - assert_eq!(payload["output"], "[REDACTED]"); + assert_eq!(payload["outputTruncated"], true); + assert_eq!( + payload["output"].as_str().unwrap().as_bytes().len(), + MAX_OUTPUT_BYTES + ); assert!(!payload.to_string().contains("secret")); assert!(payload["outputDigest"] .as_str() diff --git a/server/src/services/native-runtime/native-session-executor.test.ts b/server/src/services/native-runtime/native-session-executor.test.ts index 1982c5aa5c5..7cdc018989a 100644 --- a/server/src/services/native-runtime/native-session-executor.test.ts +++ b/server/src/services/native-runtime/native-session-executor.test.ts @@ -298,6 +298,20 @@ describe("verified native harness backups", () => { }, }); + const continuationExecution = { + ...backupExecution, + binding: { + ...backupExecution.binding, + runId: "run-2", + executionWorkspaceId: "run-2", + }, + } as NativeExecutionInputV1; + expect(verifyNativeHarnessBackup({ + root, + execution: continuationExecution, + runnerInstanceId: "runner-1", + })).not.toBeNull(); + await writeFile(join(current, "codex-home", "sessions", "thread.jsonl"), "corrupt"); expect(verifyNativeHarnessBackup({ root, diff --git a/server/src/services/native-runtime/native-session-executor.ts b/server/src/services/native-runtime/native-session-executor.ts index e9b4199048e..5e60a1a1ae3 100644 --- a/server/src/services/native-runtime/native-session-executor.ts +++ b/server/src/services/native-runtime/native-session-executor.ts @@ -923,8 +923,9 @@ function nativeHarnessEnvironmentFingerprint( .update( canonicalJson({ companyId: execution.binding.companyId, + agentId: execution.binding.agentId, + issueId: execution.binding.issueId, normalizedSessionId: nativeSessionKey(execution), - executionWorkspaceId: execution.binding.executionWorkspaceId, workspace: { cwd: execution.workspace.cwd, repoUrl: execution.workspace.repoUrl,