fix(host_agent): gate "deployed" on agent check-in; fail no-op runs
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 9s
CI / integration (push) Successful in 2m18s
CI / publish (push) Successful in 58s

A failed provision looked successful in two ways:

1. The host panel showed the agent as deployed (metrics + Update/Rotate/Remove)
   because provision/deploy mint the registration row BEFORE the playbook runs
   and the panel keyed "installed" on that row. Now gated on the agent actually
   checking in (reg.last_seen_at). Three states: reporting (metrics + lifecycle),
   pending (token minted but no check-in → "no metrics yet, deploy may be
   running/failed" banner + retry + Clear pending registration), and none
   (install path).

2. The run reported success though nothing ran — ansible-playbook exits 0 on
   "no hosts matched"/empty inventory. The executor now treats an empty PLAY
   RECAP (returncode 0 but no hosts executed) as failed, with a clear failure
   note. Non-zero exits and recap failed/unreachable were already caught.

Scribe issue #887.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 09:50:11 -04:00
parent a92d1995d5
commit bb90411f00
3 changed files with 43 additions and 9 deletions
+12 -1
View File
@@ -442,10 +442,21 @@ async def start_run(
await _flush()
await proc.wait()
recap = parse_recap(_run_lines.get(run_id, []))
if run_id in _cancelled:
final_status = "cancelled"
elif proc.returncode != 0:
final_status = "failed"
elif not recap:
# ansible-playbook exits 0 on "no hosts matched" / empty inventory
# — nothing actually ran, so don't report it as a success.
final_status = "failed"
if len(failures) < _FAILURE_CAP:
failures.append(
"No hosts matched — nothing executed "
"(check the host's Ansible target and inventory).")
else:
final_status = "success" if proc.returncode == 0 else "failed"
final_status = "success"
finally:
shutil.rmtree(tmpdir, ignore_errors=True)