From ff245787658df5bb90ee89eac0cc87e583fef650 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Mon, 7 Sep 2026 21:58:01 -0500 Subject: [PATCH] fix(runner): initialize Rustls crypto provider (#13023) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip runs AI agents through local and remote execution adapters. > - The native runner daemon uses Rustls for secure transport. > - The production dependency graph enables the `ring` and `aws-lc-rs` crypto backends. > - Rustls cannot select a default backend when both backends are active. > - The runner daemon did not select a backend before it built a TLS client configuration. > - This pull request installs the workspace-selected `ring` provider during process startup. > - The benefit is that the runner can start reliably with the production feature graph. ## Linked Issues or Issue Description **What happened?** `paperclip-runnerd` exited with code 101 before it opened a provider session. Rustls reported that it could not select a process-level `CryptoProvider` because the binary included two crypto backends. **Expected behavior** The runner daemon must select its configured crypto provider before it creates a TLS client configuration. The daemon must start and open the provider session. **Steps to reproduce** 1. Build `paperclip-runnerd` with the locked production dependency graph. 2. Start the daemon through the local loopback transport. 3. Observe the Rustls provider-selection panic before this change. **Paperclip version or commit** `d8b958053` on `master`. **Deployment mode** Self-hosted server. **Installation method** Built from source. **Agent adapter(s) involved** Codex through `paperclip_runner`. **Operating system** Linux 7.0.0-1010-aws on aarch64. ## What Changed - Install the Rustls `ring` provider before runner setup reaches TLS initialization. - Accept an existing process-level provider as an initialized state. - Add a focused regression test for TLS builder creation and repeated initialization. ## Verification - `cargo test --manifest-path packages/paperclip-runner/runner/Cargo.toml --locked -p paperclip-runner-core --bin paperclip-runnerd startup_installs_a_crypto_provider_before_tls_initialization` - `cargo test --manifest-path packages/paperclip-runner/runner/Cargo.toml --locked -p paperclip-runner-core --test local_runner runnerd_startup_reports_build_metadata_without_panicking` - `cargo test --manifest-path packages/paperclip-runner/runner/Cargo.toml --locked -p paperclip-runner-core --test local_runner happy_path_emits_one_result_and_one_terminal` - `cargo fmt --manifest-path packages/paperclip-runner/runner/Cargo.toml --all -- --check` - Built the debug daemon and ran `paperclip-runnerd --build-metadata` successfully. - `pnpm -r typecheck` - `pnpm build` ## Risks - Low risk. The change selects the crypto provider that the workspace already declares. - A host process can install a provider first. The runner accepts that initialized state. - No database, API, UI, telemetry, observability, or run-log contract changes. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex with model `gpt-5`. The context-window size is not exposed. The model used reasoning, repository tools, code execution, and test execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip --- .../runner-core/src/bin/paperclip-runnerd.rs | 61 +++++++++++++------ .../crates/runner-core/tests/local_runner.rs | 13 ++++ 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/bin/paperclip-runnerd.rs b/packages/paperclip-runner/runner/crates/runner-core/src/bin/paperclip-runnerd.rs index 81421d8866..a89ec5a048 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/bin/paperclip-runnerd.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/bin/paperclip-runnerd.rs @@ -104,6 +104,14 @@ fn install_diagnostic_panic_hook(directory: Option) { })); } +fn install_crypto_provider() { + // The production dependency graph enables both rustls crypto backends. + // Select the backend declared by this workspace before any TLS builder + // asks rustls for the process-level default. An embedding process may have + // already selected a provider, which is also a valid initialized state. + let _ = rustls::crypto::ring::default_provider().install_default(); +} + fn build_metadata() -> serde_json::Value { json!({ "schema": RUNNERD_BUILD_METADATA_SCHEMA, @@ -359,6 +367,28 @@ fn run(args: &[String]) -> Result<(), LocalRunnerError> { }) } +fn run_main(args: Vec) -> ExitCode { + let diagnostics_directory = diagnostic_directory(&args); + install_diagnostic_panic_hook(diagnostics_directory.clone()); + install_crypto_provider(); + match run(&args) { + Ok(()) => ExitCode::SUCCESS, + Err(error) => { + let message = format!("paperclip-runnerd: {error}"); + if let Some(directory) = diagnostics_directory { + if let Err(persist_error) = persist_runner_diagnostic(&directory, &message) { + eprintln!( + "paperclip-runnerd: failed to persist bounded diagnostic: {persist_error}" + ); + } + } else { + eprintln!("{message}"); + } + ExitCode::FAILURE + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -374,6 +404,17 @@ mod tests { ); } + #[test] + fn startup_installs_a_crypto_provider_before_tls_initialization() { + let _ = run_main(vec!["--build-metadata".to_owned()]); + assert!(rustls::crypto::CryptoProvider::get_default().is_some()); + + // Startup is process-global. A repeated startup call must remain + // safe when a provider was selected earlier in the process lifetime. + let _ = run_main(vec!["--build-metadata".to_owned()]); + let _ = rustls::ClientConfig::builder(); + } + #[test] fn persistent_diagnostic_is_private_bounded_and_redacted() { let unique = format!( @@ -422,23 +463,5 @@ mod tests { } fn main() -> ExitCode { - let args = std::env::args().skip(1).collect::>(); - let diagnostics_directory = diagnostic_directory(&args); - install_diagnostic_panic_hook(diagnostics_directory.clone()); - match run(&args) { - Ok(()) => ExitCode::SUCCESS, - Err(error) => { - let message = format!("paperclip-runnerd: {error}"); - if let Some(directory) = diagnostics_directory { - if let Err(persist_error) = persist_runner_diagnostic(&directory, &message) { - eprintln!( - "paperclip-runnerd: failed to persist bounded diagnostic: {persist_error}" - ); - } - } else { - eprintln!("{message}"); - } - ExitCode::FAILURE - } - } + run_main(std::env::args().skip(1).collect()) } diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/local_runner.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/local_runner.rs index 91cba4d71d..e71ef5778a 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/local_runner.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/local_runner.rs @@ -68,6 +68,19 @@ fn event_type(message: &Value) -> Option<&str> { .flatten() } +#[test] +fn runnerd_startup_reports_build_metadata_without_panicking() { + let output = Command::new(env!("CARGO_BIN_EXE_paperclip-runnerd")) + .arg("--build-metadata") + .output() + .expect("runner daemon should start"); + + assert!(output.status.success()); + let metadata: Value = + serde_json::from_slice(&output.stdout).expect("build metadata should be valid JSON"); + assert_eq!(metadata["binaryName"], "paperclip-runnerd"); +} + #[test] fn happy_path_emits_one_result_and_one_terminal() { let commands = [