fix(ops-log): a sub-tool must not drop the claim wrapping its caller
deploy-stack.sh claims and releases around its own work. When the agent already held a longer claim for a multi-step operation, the deploy refreshed it, then released it on exit -- silently dropping the protection partway through the very operation it was guarding. Caught live: a 45-minute claim on nh3-docker/althing-post-office, taken to cover a build-push-deploy-verify rollout, was gone by the time the rollout finished. Nothing refused anything, and nothing said so. `ops-log claim` now exits 10 when the claim was already the caller's, and leaves the holder file untouched. deploy-stack.sh treats 10 as "not mine to release". Untouched matters as much as the exit code: a refresh would overwrite the reason and TTL the original claimant chose, so a deliberate 45-minute "3.7.0 rollout in progress" would degrade into "deploy-stack.sh <host> <stack>" and whoever got refused would read the wrong story. Verified in three states: no pre-existing claim -> deploy claims and releases; caller's own wider claim -> deploy refreshes nothing and leaves it standing with its original reason; another agent's claim -> still refused with exit 3.
This commit is contained in:
@@ -131,7 +131,13 @@ if [ -x "$OPS_LOG" ] && [ "${DEPLOY_NO_CLAIM:-0}" != 1 ]; then
|
||||
"$OPS_LOG" claim "$HOST" "$STACK" --ttl "${DEPLOY_CLAIM_TTL:-30m}" \
|
||||
--why "deploy-stack.sh $HOST $STACK" -q || claim_rc=$?
|
||||
case "$claim_rc" in
|
||||
0) CLAIMED=1 ;;
|
||||
0) CLAIMED=1 ;;
|
||||
10) # We only refreshed a claim this agent already held for a wider
|
||||
# operation. Do NOT release it on the way out — dropping someone's
|
||||
# multi-step claim mid-operation is exactly the exposure the claim
|
||||
# exists to prevent.
|
||||
CLAIMED=0
|
||||
echo "note: $HOST/$STACK was already claimed by you — leaving that claim in place." ;;
|
||||
3) echo "error: refused — see the claim above. Wait for the holder, coordinate" >&2
|
||||
echo " on althing, or override with DEPLOY_NO_CLAIM=1 if it is dead." >&2
|
||||
exit 3 ;;
|
||||
|
||||
@@ -54,6 +54,8 @@ EXIT CODES
|
||||
0 success / claim acquired / nothing to report
|
||||
2 usage error
|
||||
3 claim refused (held by another agent)
|
||||
10 claim REFRESHED — you already held it. The caller did NOT acquire it and
|
||||
must NOT release it on the way out (see deploy-stack.sh).
|
||||
4 audit found unlogged changes
|
||||
5 audit could not reach every host (incomplete — NOT the same as clean)
|
||||
"""
|
||||
@@ -246,6 +248,17 @@ def cmd_claim(args) -> int:
|
||||
_drop_claim(args.host, args.target)
|
||||
record(args.host, "claim-broken", args.target,
|
||||
outcome="changed" if state == "theirs" else "ok", detail=detail)
|
||||
if state == "mine":
|
||||
# Already ours: leave the holder file ALONE. A sub-tool refreshing a
|
||||
# claim would otherwise overwrite the reason and TTL the original
|
||||
# claimant chose — so a 45-minute "rollout in progress" becomes
|
||||
# "deploy-stack.sh <host> <stack>", and whoever gets refused reads the
|
||||
# wrong story about why. Report 10 and change nothing.
|
||||
if not args.quiet:
|
||||
print(f"already claimed by you: {args.host}/{args.target} "
|
||||
f"({(holder or {}).get('why') or 'no reason given'}, "
|
||||
f"{holder_age(holder)})")
|
||||
return 10
|
||||
d = CLAIMS_DIR / claim_key(args.host, args.target)
|
||||
try:
|
||||
d.mkdir(parents=True, exist_ok=False) # atomic: this IS the acquire
|
||||
@@ -269,6 +282,12 @@ def cmd_claim(args) -> int:
|
||||
if not args.quiet:
|
||||
print(f"claimed {args.host}/{args.target} for {args.ttl} "
|
||||
f"({holder['why'] or 'no reason given'})")
|
||||
# Exit 10 when this only REFRESHED a claim the caller already held. A tool
|
||||
# that claims-then-releases around its own work must not drop a longer
|
||||
# claim wrapping a multi-step operation. Measured 2026-09-19: a 45-minute
|
||||
# operation claim on nh3-docker/althing-post-office was silently released
|
||||
# by deploy-stack.sh's exit trap partway through the rollout it was
|
||||
# protecting.
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user