← Back to task

Commit 83c69a22

commit 83c69a22d7228eaf54e77e14510c517f091af119
Author: Coder Agent <coder@agents.omni>
Date:   Thu Feb 12 16:27:47 2026

    Omni/Bild+Ide: exit code 2 for nothing-to-build
    
    - bild now exits 2 for 'nothing to build' (no targets or all non-buildable)
    - previously exited 1, same as build failure, forcing callers to grep stderr
    - add explicit check when all targets are skipped (was silently exiting 0)
    - dev-review-release.sh checks exit code instead of fragile string matching
    - document bild exit codes in README (0=ok, 1=fail, 2=nothing, 124=timeout)
    
    Task-Id: t-589

diff --git a/Omni/Bild.hs b/Omni/Bild.hs
index 2f88ac3f..dd268b56 100755
--- a/Omni/Bild.hs
+++ b/Omni/Bild.hs
@@ -269,11 +269,13 @@ doBild opts = do
     then do
       analysis <- analyzeAll True namespaces
       if Map.null analysis
-        then Log.wipe >> Log.fail ["bild", "nothing to build"] >> Log.br >> Exit.exitWith (Exit.ExitFailure 1)
+        then Log.wipe >> Log.fail ["bild", "nothing to build"] >> Log.br >> Exit.exitWith (Exit.ExitFailure 2)
         else putJSON analysis
     else do
       when (null allNamespaces) <| do
-        Log.wipe >> Log.fail ["bild", "nothing to build"] >> Log.br >> Exit.exitWith (Exit.ExitFailure 1)
+        Log.wipe >> Log.fail ["bild", "nothing to build"] >> Log.br >> Exit.exitWith (Exit.ExitFailure 2)
+      when (null namespaces) <| do
+        Log.wipe >> Log.fail ["bild", "nothing buildable (all targets skipped)"] >> Log.br >> Exit.exitWith (Exit.ExitFailure 2)
       nproc <- GHC.getNumProcessors
       createHier root
       let runWithManager action =
diff --git a/Omni/Bild/README.md b/Omni/Bild/README.md
index 3e67ce0f..9a30ef6c 100644
--- a/Omni/Bild/README.md
+++ b/Omni/Bild/README.md
@@ -9,6 +9,15 @@ bild --time 0 Omni/Cloud.nix     # Build with no timeout
 bild --plan Omni/Test.hs         # Analyze build without building
 ```
 
+## Exit Codes
+
+| Code | Meaning |
+|------|---------|
+| 0    | Build succeeded (or all targets were already cached) |
+| 1    | Build or test failure |
+| 2    | Nothing to build (no targets found, or all targets are non-buildable extensions like `.sh`, `.md`) |
+| 124  | Timeout |
+
 When the executable is built, the output will go to `_/bin`. Example:
 
 ```bash
diff --git a/Omni/Ide/DEV_REVIEW_RELEASE.md b/Omni/Ide/DEV_REVIEW_RELEASE.md
index 3fdd38fa..6e78bfe3 100644
--- a/Omni/Ide/DEV_REVIEW_RELEASE.md
+++ b/Omni/Ide/DEV_REVIEW_RELEASE.md
@@ -94,7 +94,8 @@ Cost accounting is recorded per run in task comments:
 - `--max-task-cost` prevents additional automated runs once the task exceeds the limit.
 
 Namespace verification notes:
-- Dev build gating treats `bild` "nothing to build" / known parse-only namespace errors as non-buildable and skips verification instead of blocking progress.
+- `bild` exits with code 2 when there is nothing to build (no targets or all targets are non-buildable extensions like `.sh`, `.md`).
+- Dev build gating treats exit code 2 as a skip rather than a failure.
 
 Expected lifecycle:
 
diff --git a/Omni/Ide/Workflows/integrator.md b/Omni/Ide/Workflows/integrator.md
index 8f62329e..15c023d4 100644
--- a/Omni/Ide/Workflows/integrator.md
+++ b/Omni/Ide/Workflows/integrator.md
@@ -71,8 +71,7 @@ Run namespace-specific checks:
 - `bild <namespace>`
 - `bild --test <namespace>` where available
 
-If `bild` reports a non-buildable namespace condition (for example `nothing to build`
-or the known container parse error `parsing Int failed, expected Number, but encountered String`),
+If `bild` exits with code 2 (nothing to build / non-buildable namespace),
 record verification as skipped/N/A instead of blocking integration.
 
 If verification fails for other reasons:
diff --git a/Omni/Ide/dev-review-release.sh b/Omni/Ide/dev-review-release.sh
index 0ac8497d..f6ff5cbd 100755
--- a/Omni/Ide/dev-review-release.sh
+++ b/Omni/Ide/dev-review-release.sh
@@ -339,9 +339,10 @@ run_cost_cents() {
   agentd status "$run_name" --json 2>/dev/null | jq -r '.cost_cents // 0' 2>/dev/null || echo "0"
 }
 
-is_non_buildable_namespace_error() {
-  local output="$1"
-  grep -Eq "nothing to build|parsing Int failed, expected Number, but encountered String" <<<"$output"
+is_non_buildable_exit_code() {
+  local rc="$1"
+  # bild exits 2 when there is nothing to build (no targets or all targets skipped).
+  [[ "$rc" -eq 2 ]]
 }
 
 cost_limit_comment_exists() {
@@ -786,16 +787,16 @@ run_single_task() {
       local build_passed="true"
       if [[ -n "$task_namespace" ]]; then
         log "Running build verification for namespace: $task_namespace"
-        local build_output
+        local build_output build_rc=0
         if build_output=$(cd "$workspace" && bild "$task_namespace" 2>&1); then
           log "Build verification passed for $task_namespace"
         else
-          if is_non_buildable_namespace_error "$build_output"; then
-            log "Namespace $task_namespace is non-buildable in this environment; skipping build verification"
-            task comment "$tid" "Automation (dev) skipped build verification for non-buildable namespace $task_namespace (bild reported no build target)." --json >/dev/null || true
+          build_rc=$?
+          if is_non_buildable_exit_code "$build_rc"; then
+            log "Namespace $task_namespace is non-buildable (bild exit 2); skipping build verification"
           else
             build_passed="false"
-            log "Build verification FAILED for $task_namespace"
+            log "Build verification FAILED for $task_namespace (bild exit $build_rc)"
             task comment "$tid" "Automation (dev) build verification failed for $task_namespace. Output:
 
 \`\`\`