From 7d864828b52bc0892fed758fc85f5fa750ce3729 Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Wed, 23 Sep 2026 16:24:56 +0800 Subject: [PATCH] fix(agentctl): treat unset AGENTCTL_FENCE as zero for bootstrap --- scripts/agentctl.py | 5 ++++- scripts/tests/test_agentctl.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/scripts/agentctl.py b/scripts/agentctl.py index c7907b59..956888d2 100755 --- a/scripts/agentctl.py +++ b/scripts/agentctl.py @@ -897,8 +897,11 @@ def inherited(repo, required=None): or alive_status(row['pid'], row['start'], row['boot']) != 'alive' or descent_status(row['pid']) != 'yes'): deny('STALE_GENERATION', 'Lease identity is stale or caller is not a supervised descendant') + # Unset env equates to fence 0 so pre-upgrade supervisors / self-bootstrap + # ff hooks (which re-exec the updated agentctl.py mid-merge) stay compatible. expected_fence = str(row['fence'] if 'fence' in row.keys() else 0) - if os.getenv('AGENTCTL_FENCE') != expected_fence: + observed_fence = os.getenv('AGENTCTL_FENCE', '0') + if observed_fence != expected_fence: deny('STALE_GENERATION', 'Lease fence is stale or missing', expected=expected_fence, observed=os.getenv('AGENTCTL_FENCE')) if required: diff --git a/scripts/tests/test_agentctl.py b/scripts/tests/test_agentctl.py index 2eb660c6..f62dc77b 100644 --- a/scripts/tests/test_agentctl.py +++ b/scripts/tests/test_agentctl.py @@ -1024,6 +1024,18 @@ class AgentctlTests(unittest.TestCase): self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr) self.assertIn("STALE_GENERATION", result.stderr) + def test_unset_fence_env_matches_default_zero(self): + """Self-bootstrap: hooks re-exec updated agentctl without AGENTCTL_FENCE set.""" + task = self.initialized_task() + code = ( + "import os,subprocess,sys;" + "os.environ.pop('AGENTCTL_FENCE', None);" + f"p=subprocess.run([sys.executable,{str(self.root / 'scripts/agentctl.py')!r}," + "'guard','resource','path:src/a']);sys.exit(p.returncode)" + ) + result = self.run_lease("alpha", ["path:src/a"], code, cwd=task) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + def test_recover_git_dev_requires_verification_marker(self): task = self.initialized_task(capabilities=("git:dev",)) self._seed_run("git-dev-run", "alpha", ["git:dev"], "recovery_required",