fix(core): Add structured error handling and responsive lock release

Improves core infrastructure with robot-friendly error output and
faster lock release for better sync behavior.

Error handling improvements (error.rs):
- ErrorCode::exit_code(): Unique exit codes per error type (1-13)
  for programmatic error handling in scripts/agents
- GiError::suggestion(): Helpful hints for common error recovery
- GiError::to_robot_error(): Structured JSON error conversion
- RobotError/RobotErrorOutput: Serializable error types with code,
  message, and optional suggestion fields

Lock improvements (lock.rs):
- Heartbeat thread now polls every 100ms for release flag, only
  updating database heartbeat at full interval (5s default)
- Eliminates 5-10s delay after sync completion when waiting for
  heartbeat thread to notice release
- Reduces lock hold time after operation completes

Database (db.rs):
- Bump expected schema version to 6 for MR migration

The exit code mapping enables shell scripts and CI/CD pipelines to
distinguish between configuration errors (2-4), GitLab API errors
(5-8), and database errors (9-11) for appropriate retry/alert logic.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Taylor Eernisse
2026-01-26 22:46:08 -05:00
parent cd44e516e3
commit 5fe76e46a3
3 changed files with 109 additions and 9 deletions

View File

@@ -42,10 +42,7 @@ pub struct AppLock {
impl AppLock {
/// Create a new app lock instance.
pub fn new(conn: Connection, options: LockOptions) -> Self {
let db_path = conn
.path()
.map(PathBuf::from)
.unwrap_or_default();
let db_path = conn.path().map(PathBuf::from).unwrap_or_default();
Self {
conn,
@@ -73,7 +70,9 @@ impl AppLock {
let now = now_ms();
// Use IMMEDIATE transaction to prevent race conditions
let tx = self.conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
let tx = self
.conn
.transaction_with_behavior(TransactionBehavior::Immediate)?;
// Check for existing lock within the transaction
let existing: Option<(String, i64, i64)> = tx
@@ -176,9 +175,21 @@ impl AppLock {
}
};
loop {
thread::sleep(interval);
// Poll frequently for early exit, but only update heartbeat at full interval
const POLL_INTERVAL: Duration = Duration::from_millis(100);
loop {
// Sleep in small increments, checking released flag frequently
let mut elapsed = Duration::ZERO;
while elapsed < interval {
thread::sleep(POLL_INTERVAL);
elapsed += POLL_INTERVAL;
if released.load(Ordering::SeqCst) {
return;
}
}
// Check once more after full interval elapsed
if released.load(Ordering::SeqCst) {
break;
}