Summary
write_claim decides which table row is a task's claim by scanning the whole line for the
task id. Roadmaps that carry a Depends column repeat ids across rows, so most tasks match two or
more rows, acquire refuses to guess, and the durable claim tag is never written — while the
lease is granted normally.
The result is the state references/roadmap.md names as the thing to avoid:
A lease without a claim tag is invisible to anyone reading the repository.
Exclusion still holds. Visibility does not, and nothing about the run reports it as degraded.
Measured
Four-repository project (nicegram-business umbrella + three service submodules), agent-sync
1.3.5, claimTags: {"docs/ROADMAP.md": {"mode": "cell", "cell": -1, ...}}, boards shaped
| id | what | acceptance | depends | decisions | status |:
| board |
task ids |
ids matching exactly one row |
claim writable |
account-factory |
11 |
3 |
27% |
account-session-connect |
106 |
25 |
24% |
nicegram-business-app |
82 |
14 |
17% |
The ids that do work are the ones nothing depends on — i.e. mostly leaf tasks. The more central
a task is, the less likely its claim is recorded.
Reproduction
| **AF-005** | Service skeleton | … | AF-001 | … | `todo` |
| **AF-006** | Schema and migrations | … | AF-005 | … | `todo` |
$ agent_sync.py acquire AF-005
docs/ROADMAP.md: `AF-005` appears in 2 table rows — refusing to guess which one is the claim.
Narrow the pattern or edit by hand
won AF-005 (run r-…, ttl 2700s)
Lease won, roadmap untouched. AF-005 is the second row's dependency, not its claim.
Cause
scripts/agent_sync.py, write_claim (~L1187):
hits = [i for i, l in enumerate(lines)
if re.search(rf"(?<![A-Za-z0-9-]){re.escape(key)}(?![A-Za-z0-9-])", l)
and self._row_cells(l) is not None]
The word-boundary match is right; its scope is the whole line. _row_cells is only used to
confirm the line is a row, never to say where in the row the id must appear.
The refusal message suggests "narrow the pattern", but claimTags keys are file globs — there is
no pattern that selects a row. The only available workarounds are editing the roadmap by hand
(which is what the lease exists to prevent) or dropping the Depends column.
Proposed fix
An optional idCell beside cell, 0-based with the same negative indexing, restricting the
search to one cell:
"apps/*/docs/ROADMAP.md": { "mode": "cell", "cell": -1, "idCell": 0,
"held": "{prev} (claimed: {holder})" }
def _matches(line):
cells = self._row_cells(line)
if cells is None:
return False
hay = line if id_cell is None else (cells[id_cell] if -len(cells) <= id_cell < len(cells) else "")
return re.search(rf"(?<![A-Za-z0-9-]){re.escape(key)}(?![A-Za-z0-9-])", hay) is not None
Backward compatible: idCell unset keeps today's whole-row behaviour, so no existing config
changes meaning. check can validate it the same way it validates cell.
Two smaller things that would help independently of the above:
check could surface the ratio. It already refuses a claim-tag pattern with nothing to
look for; a board where most ids are ambiguous is the same class of defect and is invisible
until the first acquire.
acquire prints the refusal above the won line, so a | tail -3 hides it. Reporting the
claim outcome after the lease result — or in the same line — would make it harder to miss.
Happy to open a PR for idCell if the shape looks right.
Summary
write_claimdecides which table row is a task's claim by scanning the whole line for thetask id. Roadmaps that carry a
Dependscolumn repeat ids across rows, so most tasks match two ormore rows,
acquirerefuses to guess, and the durable claim tag is never written — while thelease is granted normally.
The result is the state
references/roadmap.mdnames as the thing to avoid:Exclusion still holds. Visibility does not, and nothing about the run reports it as degraded.
Measured
Four-repository project (
nicegram-businessumbrella + three service submodules), agent-sync1.3.5,claimTags: {"docs/ROADMAP.md": {"mode": "cell", "cell": -1, ...}}, boards shaped| id | what | acceptance | depends | decisions | status |:account-factoryaccount-session-connectnicegram-business-appThe ids that do work are the ones nothing depends on — i.e. mostly leaf tasks. The more central
a task is, the less likely its claim is recorded.
Reproduction
Lease won, roadmap untouched.
AF-005is the second row's dependency, not its claim.Cause
scripts/agent_sync.py,write_claim(~L1187):The word-boundary match is right; its scope is the whole line.
_row_cellsis only used toconfirm the line is a row, never to say where in the row the id must appear.
The refusal message suggests "narrow the pattern", but
claimTagskeys are file globs — there isno pattern that selects a row. The only available workarounds are editing the roadmap by hand
(which is what the lease exists to prevent) or dropping the
Dependscolumn.Proposed fix
An optional
idCellbesidecell, 0-based with the same negative indexing, restricting thesearch to one cell:
Backward compatible:
idCellunset keeps today's whole-row behaviour, so no existing configchanges meaning.
checkcan validate it the same way it validatescell.Two smaller things that would help independently of the above:
checkcould surface the ratio. It already refuses a claim-tag pattern with nothing tolook for; a board where most ids are ambiguous is the same class of defect and is invisible
until the first
acquire.acquireprints the refusal above thewonline, so a| tail -3hides it. Reporting theclaim outcome after the lease result — or in the same line — would make it harder to miss.
Happy to open a PR for
idCellif the shape looks right.