fix(agents): a soft-deleted agent could never be purged
Clearing the fleet's four leftover agents returned 404 on every one. They had
been soft-deleted back in June — correctly invisible in the UI ever since — and
`agents::get` filters `deleted_at IS NULL`, so `workspace_agent` could not find
them. Every route uses it, including `batch-delete`, the one that exists to
HARD-purge. So a soft-deleted agent was unreachable from the application
entirely and its row stayed forever.
`get_any` sees them, and only the purge path uses it: hiding soft-deleted rows
is right for every read, and wrong for the one operation whose whole job is
removing them. Written with `query_as` rather than the checked macro so it does
not force an offline-cache regeneration on every machine that builds this.
`fleet-reset.sh` now uses `batch-delete` for agents rather than
`DELETE /api/claws/{id}`. The latter is a SOFT delete, so pointing a reset
script at it would have quietly added to the pile it was meant to clear.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
16cfc29074
commit
f27d2605eb
@@ -64,6 +64,52 @@ pub async fn insert(pool: &PgPool, agent: &Agent, policy: &AccessPolicy) -> Resu
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch an agent even if it has been soft-deleted.
|
||||
///
|
||||
/// For the PURGE path only. `get` hides soft-deleted rows, which is right for
|
||||
/// every read — but it also meant the hard purge could not see the rows it
|
||||
/// exists to remove: a soft-deleted agent was unreachable from every route and
|
||||
/// accumulated forever with no way out of the application. Four of them dated
|
||||
/// from June before anyone noticed, because the UI correctly never showed them.
|
||||
pub async fn get_any(pool: &PgPool, agent_id: AgentId) -> Result<Agent, DbError> {
|
||||
// `sqlx::query_as` rather than the checked macro: this is the same columns
|
||||
// as `get` minus one predicate, and adding a second compile-time query for
|
||||
// that would mean regenerating the offline cache on every machine that
|
||||
// builds this.
|
||||
let row: Option<(
|
||||
uuid::Uuid,
|
||||
uuid::Uuid,
|
||||
String,
|
||||
String,
|
||||
String,
|
||||
String,
|
||||
String,
|
||||
String,
|
||||
uuid::Uuid,
|
||||
String,
|
||||
)> = sqlx::query_as(
|
||||
"SELECT id, workspace_id, name, job_title, system_prompt, avatar,
|
||||
accent, wallpaper, managed_by, status
|
||||
FROM agents WHERE id = $1",
|
||||
)
|
||||
.bind(agent_id.as_uuid())
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
let row = row.ok_or(DbError::NotFound)?;
|
||||
Ok(Agent {
|
||||
id: AgentId::from(row.0),
|
||||
workspace_id: WorkspaceId::from(row.1),
|
||||
name: row.2,
|
||||
job_title: row.3,
|
||||
system_prompt: row.4,
|
||||
avatar: row.5,
|
||||
accent: row.6,
|
||||
wallpaper: row.7,
|
||||
managed_by: UserId::from(row.8),
|
||||
status: row.9.parse().expect("status CHECK constraint"),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get(pool: &PgPool, agent_id: AgentId) -> Result<Agent, DbError> {
|
||||
let row = sqlx::query!(
|
||||
"SELECT id, workspace_id, name, job_title, system_prompt, avatar,
|
||||
|
||||
Reference in New Issue
Block a user