Return 404 when a worker's originating run is missing

The worker folder-target guard opened a run reader and mapped every
failure, including a run that no longer exists, to HTTP 500 with an
error log. Load the projection through the store's lookup instead so a
missing run is a 404 with its own error code, and run the check after
environment selection so ordinary environment errors are reported
first.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Scott Werner 2026-09-04 16:20:23 -04:00
parent b12eb8d84e
commit da0b4c259a
3 changed files with 47 additions and 14 deletions

View file

@ -46,6 +46,8 @@ pub(crate) enum RunIntentAdmissionError {
#[source]
source: fabro_store::Error,
},
#[error("originating worker run `{run_id}` was not found")]
WorkerRunNotFound { run_id: RunId },
}
#[derive(Debug, Error)]

View file

@ -631,9 +631,6 @@ pub(crate) async fn create_run_from_intent(
Ok(validated) => validated,
Err(error) => return run_intent_admission_error(error.into()),
};
if let Err(error) = validate_intent_actor_target(&state, &actor, &target).await {
return run_intent_admission_error(error);
}
let environment_id = match select_intent_environment_id(
&state,
intent
@ -644,6 +641,9 @@ pub(crate) async fn create_run_from_intent(
Ok(id) => id,
Err(error) => return run_intent_admission_error(error.into()),
};
if let Err(error) = validate_intent_actor_target(&state, &actor, &target).await {
return run_intent_admission_error(error);
}
let blobs = state.store_ref().blobs();
let version_store = fabro_workflow_version::WorkflowVersionStore::new(blobs);
let closure = match version_store.get_closure(&intent.workflow_version_id).await {
@ -1007,6 +1007,7 @@ fn run_intent_admission_error(error: RunIntentAdmissionError) -> Response {
}
RunIntentAdmissionError::Target(_)
| RunIntentAdmissionError::FolderTarget(_)
| RunIntentAdmissionError::WorkerRunNotFound { .. }
| RunIntentAdmissionError::Environment(_) => {}
}
@ -1085,6 +1086,11 @@ fn run_intent_admission_error(error: RunIntentAdmissionError) -> Response {
"failed to inspect originating worker run",
"worker_run_store_error",
),
RunIntentAdmissionError::WorkerRunNotFound { .. } => intent_error(
StatusCode::NOT_FOUND,
"originating worker run not found",
"worker_run_not_found",
),
}
}
@ -1096,23 +1102,16 @@ async fn validate_intent_actor_target(
let (Principal::Worker { run_id }, RunTarget::Folder { .. }) = (actor, target) else {
return Ok(());
};
let run_store = state
let projection = state
.stores
.runs
.open_run_reader(run_id)
.load_run_projection(run_id)
.await
.map_err(|source| RunIntentAdmissionError::WorkerRun {
run_id: *run_id,
source,
})?;
let projection =
run_store
.state()
.await
.map_err(|source| RunIntentAdmissionError::WorkerRun {
run_id: *run_id,
source,
})?;
})?
.ok_or(RunIntentAdmissionError::WorkerRunNotFound { run_id: *run_id })?;
if !projection.spec.settings.run.environment.provider.is_local() {
return Err(EnvironmentSelectionError::TargetUnsupported {
detail: "folder targets created by a worker require a Local parent environment",

View file

@ -4536,6 +4536,38 @@ async fn run_tools_worker_cannot_select_server_folder_from_clone_based_parent()
);
}
#[tokio::test]
async fn run_tools_worker_folder_target_from_missing_parent_run_is_not_found() {
let dir = tempfile::tempdir().unwrap();
let (state, app) = jwt_auth_app();
let worker_token = issue_test_run_tools_worker_token(&RunId::new());
let workflow_version_id = store_workflow_version(&state, MINIMAL_DOT, None).await;
let mut intent = folder_intent(workflow_version_id, dir.path().to_string_lossy());
intent["environment_id"] = json!("local");
let response = app
.oneshot(json_bearer_request(
Method::POST,
"/runs",
&worker_token,
&intent,
))
.await
.unwrap();
let body = response_json!(response, StatusCode::NOT_FOUND).await;
assert_eq!(body["errors"][0]["code"], "worker_run_not_found");
assert!(
state
.stores
.run_summaries
.list_identities()
.await
.unwrap()
.is_empty()
);
}
#[tokio::test]
async fn run_tools_worker_can_select_server_folder_from_local_parent() {
let dir = tempfile::tempdir().unwrap();