Skip to content

claimTags: whole-row id match makes the claim unwritable for most tasks on boards with a Depends column #1

Description

@sshlg

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:

  1. 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.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions