fix: staging CI review issues (batch 1) (#883)

* fix: address staging-ci-review issues (batch 1)

- #811: Fix unreachable error handling in worker — restructure .await?
  to explicit match on nested Result so token budget errors are properly
  logged and marked as failed
- #813: Combine metadata + token budget into single update_context()
  call to prevent concurrent worker observing partial state
- #814: Persist max_tokens and total_tokens_used to both PostgreSQL and
  libSQL backends — add V12 migration, update save_job/get_job
- #815: Cap user-supplied max_tokens at configured max_tokens_per_job
  to prevent budget bypass via metadata injection
- #869: Release locks before async I/O in webhook handler (http.rs) and
  SIGHUP handler (main.rs) to prevent blocking concurrent requests

Fixes: #811, #813, #814, #815, #869

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: address PR #883 review feedback

- Fix min(user_val, 0) bug: guard for unlimited config (max_tokens_per_job == 0)
- Remove duplicate columns from libSQL base SCHEMA (v12 migration is sole source)
- Use get_i64() helper for consistency in libsql/jobs.rs
- Add regression tests for scheduler token budget capping

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Henry Park
2026-03-10 14:01:07 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 1f5b582c5f
commit 873322f2fb
7 changed files with 226 additions and 44 deletions
+14 -8
View File
@@ -30,8 +30,9 @@ impl JobStore for LibSqlBackend {
id, conversation_id, title, description, category, status, source,
user_id,
budget_amount, budget_token, bid_amount, estimated_cost, estimated_time_secs,
actual_cost, repair_attempts, created_at, started_at, completed_at
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18)
actual_cost, repair_attempts, max_tokens, total_tokens_used,
created_at, started_at, completed_at
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20)
ON CONFLICT (id) DO UPDATE SET
title = excluded.title,
description = excluded.description,
@@ -42,6 +43,8 @@ impl JobStore for LibSqlBackend {
estimated_time_secs = excluded.estimated_time_secs,
actual_cost = excluded.actual_cost,
repair_attempts = excluded.repair_attempts,
max_tokens = excluded.max_tokens,
total_tokens_used = excluded.total_tokens_used,
started_at = excluded.started_at,
completed_at = excluded.completed_at
"#,
@@ -61,6 +64,8 @@ impl JobStore for LibSqlBackend {
estimated_time_secs,
ctx.actual_cost.to_string(),
ctx.repair_attempts as i64,
ctx.max_tokens as i64,
ctx.total_tokens_used as i64,
fmt_ts(&ctx.created_at),
fmt_opt_ts(&ctx.started_at),
fmt_opt_ts(&ctx.completed_at),
@@ -78,7 +83,8 @@ impl JobStore for LibSqlBackend {
r#"
SELECT id, conversation_id, title, description, category, status, user_id,
budget_amount, budget_token, bid_amount, estimated_cost, estimated_time_secs,
actual_cost, repair_attempts, created_at, started_at, completed_at
actual_cost, repair_attempts, max_tokens, total_tokens_used,
created_at, started_at, completed_at
FROM agent_jobs WHERE id = ?1
"#,
params![id.to_string()],
@@ -111,12 +117,12 @@ impl JobStore for LibSqlBackend {
estimated_duration: estimated_time_secs
.map(|s| std::time::Duration::from_secs(s as u64)),
actual_cost: get_decimal(&row, 12),
total_tokens_used: 0,
max_tokens: 0,
max_tokens: get_i64(&row, 14) as u64,
total_tokens_used: get_i64(&row, 15) as u64,
repair_attempts: get_i64(&row, 13) as u32,
created_at: get_ts(&row, 14),
started_at: get_opt_ts(&row, 15),
completed_at: get_opt_ts(&row, 16),
created_at: get_ts(&row, 16),
started_at: get_opt_ts(&row, 17),
completed_at: get_opt_ts(&row, 18),
transitions: Vec::new(),
metadata: serde_json::Value::Null,
extra_env: std::sync::Arc::new(std::collections::HashMap::new()),
+27 -15
View File
@@ -583,20 +583,21 @@ INSERT OR IGNORE INTO leak_detection_patterns (id, name, pattern, severity, acti
///
/// Each entry is `(version, name, sql)`. Migrations are idempotent: the
/// `_migrations` table tracks which versions have been applied.
pub const INCREMENTAL_MIGRATIONS: &[(i64, &str, &str)] = &[(
9,
"flexible_embedding_dimension",
// Rebuild memory_chunks to remove the fixed F32_BLOB(1536) type
// constraint so any embedding dimension works. Existing embeddings
// are preserved; users only need to re-embed if they change models.
//
// The vector index (libsql_vector_idx) requires a fixed-dimension
// F32_BLOB(N), so we drop it entirely. Vector search falls back to
// brute-force cosine distance which is fast enough for personal
// assistant workspaces. This matches PostgreSQL after its V9 migration.
//
// SQLite cannot ALTER COLUMN types, so we recreate the table.
r#"
pub const INCREMENTAL_MIGRATIONS: &[(i64, &str, &str)] = &[
(
9,
"flexible_embedding_dimension",
// Rebuild memory_chunks to remove the fixed F32_BLOB(1536) type
// constraint so any embedding dimension works. Existing embeddings
// are preserved; users only need to re-embed if they change models.
//
// The vector index (libsql_vector_idx) requires a fixed-dimension
// F32_BLOB(N), so we drop it entirely. Vector search falls back to
// brute-force cosine distance which is fast enough for personal
// assistant workspaces. This matches PostgreSQL after its V9 migration.
//
// SQLite cannot ALTER COLUMN types, so we recreate the table.
r#"
-- Drop vector index (requires fixed F32_BLOB(N), incompatible with flexible dimensions)
DROP INDEX IF EXISTS idx_memory_chunks_embedding;
@@ -644,7 +645,18 @@ CREATE TRIGGER IF NOT EXISTS memory_chunks_fts_update AFTER UPDATE ON memory_chu
INSERT INTO memory_chunks_fts(rowid, content) VALUES (new._rowid, new.content);
END;
"#,
)];
),
(
12,
"job_token_budget",
// Add token budget tracking columns to agent_jobs.
// SQLite supports ALTER TABLE ADD COLUMN, so no table rebuild needed.
r#"
ALTER TABLE agent_jobs ADD COLUMN max_tokens INTEGER NOT NULL DEFAULT 0;
ALTER TABLE agent_jobs ADD COLUMN total_tokens_used INTEGER NOT NULL DEFAULT 0;
"#,
),
];
/// Run incremental migrations that haven't been applied yet.
///