fix(lanes): key telemetry state per lane instance, not per lane type - #1841
Conversation
The telemetry sentinel marker was a fixed constant naming the lane TYPE, so every concurrent instance of a lane resolved the same comment on the same telemetry issue and overwrote it last-writer-wins. The creation-race reconcile already in the upsert did not help: it converges duplicate COMMENTS, not conflicting STATE written by two live lanes. The serious loss was first_drain_complete. It ends the first-drain C3 ratification gate, so one machine finishing a drain ended the earn-trust period for every other machine, widening autonomy with no human ratification. The marker now carries a lane-instance suffix, the lane-type marker becoming its prefix. The `Lane telemetry: <lane>` issue title is deliberately untouched, which is why the marker was chosen as the seam: the drain-exit snapshot, the intake sweep, and the attention view all match lane infrastructure by that title contract, and a prose-matching miss turns lane infrastructure into backlog. Partitioning is only correct while instance ids are distinct, so collision is detected rather than assumed: the state block gains lane_instance, a per-session writer_nonce, a per-cycle heartbeat_at, and paused_until. A differing nonce over a stale block is the ordinary restart adoption; over a fresh block it means another live lane holds the id, and the lane writes nothing, escalates, and stops. Detection runs before any write, so a duplicate id degrades to a stopped lane rather than a clobbered first_drain_complete. Two consumers move with it. telemetry-upsert.sh admits @ in the marker charset and in both fallback lookaround classes, so lane:x never adopts lane:x@laptop-a. restart-consumer.sh matched a bound marker by exact equality and would have gone silently blind once lanes carried the suffix -- reporting no-state forever and restarting nothing; a bound marker now names a lane type across every instance, with an optional instance key to pin one, and the scan no longer lets a quiet sibling instance mask a later instance's live restart request. Fixes #1295 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RhS3T7ShwJgKTrvk2Mvd3C
…kers Its per-lane telemetry.marker binding matched a comment by exact marker equality. Once lanes carry the writer-identity suffix, no bound lane's comment matches -- the consumer reports no-state forever and restarts nothing, the worst failure shape for an unattended relaunch trigger, since a silent no-op looks identical to a healthy quiet tick. A bound marker now names a lane TYPE and matches every writer instance of it, with the same trailing boundary that keeps work-items:work-loop from adopting work-items:work-loop-v2. A new optional telemetry.instance key pins one instance, as does writing the suffix into marker itself. The scan also no longer stops at the first matching comment when that comment is not asking: with several instances writing to one issue, a quiet sibling appearing first would mask a later instance's live restart request. Stopping early was safe only while one comment per lane existed. Fixes #1295 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RhS3T7ShwJgKTrvk2Mvd3C
…hannel
The instance resolution read a bare ${LANE_INSTANCE} env var, which the harness
never sets for a skill body -- so every lane would have silently taken the
hostname fallback and no operator-set lane_instance would ever have reached the
marker. Two lanes on one machine, the case the collision detector exists for,
would then share a hostname-derived id and escalate on their first cycle.
Skill bodies read config through ${user_config.KEY} substitution (hook-config
delivery convention, channel E), the same channel work-loop already uses for its
item-cap bounds; a surviving literal placeholder means the key is unset. The
hostname fallback stays a default rather than a sanitizer: it passes through the
same charset gate, so a hostname that cannot yield a conforming id stops the
lane instead of producing a marker nobody chose.
Also states what a lane does with the legacy un-suffixed comment: nothing. Its
marker names no writer, so no instance can prove it owns it, and a lane that
adopted it would reintroduce the shared-comment clobber. Retiring it is an
operator action; until then it reads as stale, which is honest.
Fixes #1295
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhS3T7ShwJgKTrvk2Mvd3C
babysit-loop's SKILL.md was already 495/500 lines before this branch, so adding the instance-collision branches inline pushed it to 519 and failed the hard cap in check-changed-skills. The branches belong in reference/telemetry-upsert.md regardless: that spoke already owns every other mechanic of the same upsert -- resolution, validation, lookup, POST/PATCH, race reconcile -- and splitting one procedure across a hub and a spoke is what progressive disclosure exists to avoid. SKILL.md keeps the state-block shape and what the fields are for; the spoke keeps how the check runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RhS3T7ShwJgKTrvk2Mvd3C
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aa12122ef3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Review findings on the lane-instance telemetry partition: - Clean handoff: a replacement session no longer reads a stopped predecessor's fresh block as a live collision. A non-null `restart_request` is a stopping lane's last write, so the replacement adopts immediately, clears the request, and continues instead of waiting out the two-hour staleness window. - First-cycle claim: an unclaimed marker is claimed with a cycle-0 block and re-read through the creation-race reconcile before any work, so two same-id sessions starting together stop before either overwrites the other's first durable state. - restart-consumer local binding: an instance-suffixed comment under an unpinned binding is another machine's writer, so its request now reports as `unbound-instance` and relaunches nothing — consuming it would start unintended sibling instances on every stopped consumer sharing the issue. The pinned instance's comment and the legacy un-suffixed one stay actionable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dsTvnGqMJeLTZe4DpGGVQ
source-control re-bumps to 0.44.0: 0.43.0 shipped from #1838 while this branch held the same number.
|
Claude finished @kyle-sexton's task in 4m 6s —— View job Security review — PR #1841 (lane-instance telemetry partitioning)
Scope: only the 20 files changed in this PR ( IMPORTANT —
|
|
Claude finished @kyle-sexton's task in 4m 22s —— View job Code review
Scope: correctness, architecture, error handling, test coverage, maintainability. Security review is left to the dedicated security lane per REVIEW.md. I verified that d13a9f8 correctly closes the two P1/P2 gaps chatgpt-codex-connector flagged (clean handoff on a fresh block with a non-null Finding: the convention doc (the canonical cross-plugin spec) wasn't updated with d13a9f8's protocol changes
This matters because the PR frames this convention doc as the authoritative, versioned, cross-plugin contract ("C4 structural change to a cross-plugin convention contract," §"Tier ratified as major"). A future lane implementer who builds strictly from the convention doc — rather than diffing Suggested fix: port the two extra branches (and their justification — "recording the request is a stopping lane's last write," "claiming first bounds the race to the claim itself") from Everything else checked out: the I was not able to execute the shell test suites in this sandbox ( |
The #1660 merge-conflict resolution round-tripped the file through cp1252, double-encoding every non-ASCII character; the corruption shipped to main in the #1838 squash. Restored by reversing the same round-trip; the 0.44.0 section was authored clean and is untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dsTvnGqMJeLTZe4DpGGVQ
|
Claude has reviewed this PR 3 times. The lane skips further automatic reviews after 5; deleting this comment resets the count. |
|
Claude finished @kyle-sexton's task in 3m 7s —— View job Security review — PR #1841 (lane-instance telemetry partitioning)
Scope: the 20 files changed in this PR, re-reviewed at HEAD What changed since the last review: exactly one commit, Still open —
|
|
Claude finished @kyle-sexton's task in 1m 13s —— View job Code review — PR #1841 (re-check at HEAD
|
work-loop and babysit-loop SKILL.md were at 506 and 500 lines against the 500 hard cap: tightened prose and reflowed ragged wraps with no semantic loss (both at 499). Renamed the `ba` test fixture variable family the spell gate flagged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dsTvnGqMJeLTZe4DpGGVQ
|
Claude finished @kyle-sexton's task in 1m 3s —— View job Security review — PR #1841 (re-check at HEAD
|
|
Claude finished @kyle-sexton's task in 1m 52s —— View job Code review — PR #1841 (re-check at HEAD
|
#1841 keys the telemetry marker per lane instance and adds a lane_instance validation gate ahead of it, in the same three inlined blocks this branch rewrites. Both sides kept: main's INSTANCE validation and instance-qualified MARKER, this branch's sentinel contract, payload floor, write-status check, read-back, and gated supersede pass. main also released claude-ops 0.25.0, source-control 0.44.0, and work-items 0.31.0, so this branch's entries move to 0.25.1, 0.44.1, and 0.31.1. Re-ran the six-scenario harness against all three merged blocks: the happy path supersedes duplicates silently; a failed PATCH, a degraded stored body, a degraded create, a literal @path body, and a 15-byte payload each report and none tombstone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…1850) Fixes #943 ## Summary The three lanes that inline the `gh api` telemetry upsert — `source-control:babysit-loop`, `work-items:work-loop`, `work-items:attend-queue` — wrote `$BODY_FILE` with no assertion that what they composed was usable and no check that what they sent actually landed. `claude-ops`'s `telemetry-upsert.sh` refuses a degraded body and reads the comment back; an installed plugin cannot invoke a sibling plugin's script, so the three inlined copies inherited none of that. The observed failure was a telemetry comment carrying a literal `@C:/…/telemetry_combined.txt` as its entire body across three sessions — the lane reported nothing while its `updatedAt` kept moving, so any consumer keying on the comment's timestamp rather than its body read it as fresh. ## Fix Each inlined block now carries three checks, because they catch different failures. **Pre-write gate.** Before any API call, a `$BODY_FILE` that is empty, opens with a literal `@`, is not sentinel-prefixed, or carries under 16 payload bytes below the sentinel is refused and the cycle skips the upsert fail-closed — no POST, no PATCH. The floor is measured on everything below line 1, so it matches the wrapper's `MIN_BODY_BYTES` byte-for-byte whether that line ends in LF or CRLF. The prefix assertion compares bytes rather than a whole first line, so a CRLF body is not false-rejected. **Write-status check.** The PATCH's own exit status is checked before anything trusts the result. A failed write leaves the previous cycle's body in place, which a read-back running regardless would accept — so the read-back alone cannot carry the guarantee. **Post-write read-back.** The block then re-reads what the write stored and reports the cycle UNREPORTED unless that body still opens with the sentinel and clears the same floor. This is the only check that sees a write which reported success and stored something else, and the one that would have caught #943 itself: the composed file was correct and the defect was the invocation (`-f body=@FILE` transmits the literal path; these blocks only ever use `-F body=@`). Every branch that ends without a verified body — including a degraded create, which leaves no sentinel-prefixed comment to re-read — reports and **skips the duplicate-supersede pass**, so a cycle whose own write is unproven never tombstones a racing session's comment. Supporting corrections in the same change: - The `$BODY_FILE` sentinel-first-line contract was implicit in a trailing comment. A body composed without it is not merely rejected — the lookup matches on that prefix, so it would never be found again and the next cycle would post a second comment. Now stated in prose. - The prose and CHANGELOGs claimed `morning-brief`'s freshness check is what a degraded body deceives. Verified against its `print_telemetry`: it parses `lane:`/`last-cycle:` out of the body, so a degraded comment makes the lane vanish from the report rather than look healthy. Attributed instead to any consumer keying on the comment's timestamp instead of its body. - The prose no longer justifies the read-back with an invocation the block never emits, and now states the limits it inherits rather than fixes: a PATCH that succeeds while storing the previous body still verifies, the read-back proves *some* well-formed telemetry is present rather than *this* cycle's, and every inline branch exits 0 and reports through stderr alone. Deliberately not replicated from the wrapper: the 64 KiB cap, the body-file containment checks, retries, and its distinct non-zero exit codes. Both surfaces now say which guarantees travel with which. ## Verification The three blocks are byte-identical apart from their `MARKER` line (checked mechanically, not by eye). Each was extracted and driven against a stubbed `gh`, once per lane: | scenario | notice on stderr | duplicates superseded | | --- | --- | --- | | PATCH succeeds, stored body fresh | silent | yes | | **PATCH fails, comment still holds the previous cycle** | **UNREPORTED** | no | | PATCH succeeds, stored body degraded | UNREPORTED | no | | first-ever POST lands degraded, nothing to verify | UNREPORTED | no | | body is a literal `@path` | refused, no API call | no | | sentinel with a 15-byte payload | refused, no API call | no | Row 2 is the one an independent review found silent before this change: the write failed, the read-back re-read a well-formed *previous* body, and the cycle reported success over a stale comment. The block was also driven under `set -e` with a failing create: it reaches the "no comment available to write to" notice and exits 0, rather than aborting before any notice. That is what the bare POST's `|| true` buys, and it is what keeps the "every branch reports" claim true inside a strict-mode wrapper. `SENTINEL_OK` was also driven directly across the floor boundary: sentinel-only, 15-byte and 16-byte payloads, LF and CRLF, a non-sentinel 51-byte body, and a sentinel that is not the first line. LF and CRLF now flip at the same 15/16 boundary, which is what "matches `MIN_BODY_BYTES`" requires; `lane: from-stdin` is the same 16-byte string `telemetry-upsert.test.sh` uses to pin that floor. Repo gates, run locally against `origin/main` and green in CI on this branch: - `scripts/check-changed-skills.sh origin/main` — PASS (`plugin-gate`) - `scripts/check-changelog-parity.sh --check` / `--check-bump` / `--check-order` — PASS - `markdownlint-cli2` over the changed markdown — 0 issues Version bumps are re-derived on every merge from `main`, which released all three plugins while this branch was open. The entries currently sit at `claude-ops` 0.25.1, `source-control` 0.44.1, and `work-items` 0.31.1, each above the newest upstream entry in its own CHANGELOG. If `main` releases any of them again before this merges, the top entry needs renumbering once more — the collision is in the CHANGELOG heading and the manifest version, not in the change itself. This branch merges `main` rather than rebasing, so the feature commits stay reachable and every push is a fast-forward. #1841 landed mid-review and keys the marker per lane instance in these same three blocks; both sides are kept — its `INSTANCE` validation and instance-qualified `MARKER`, this branch's gate, write check, read-back, and gated supersede pass. `work-loop`'s SKILL.md sat at 499 of its 500-line hard cap on `main`, so these additions tripped the skill-quality gate. Its upsert moves to `reference/telemetry-upsert.md` — the same shape the sibling `babysit-loop` lane already uses for the identical block — leaving SKILL.md at 437 lines with the telemetry home and durable-state contract, pointing at the spoke for the mechanism. The block moves verbatim; the scenario table above was re-run against the spoke. ## Follow-ups deliberately not taken here - Every inline branch exits 0 and signals through stderr only; the wrapper uses distinct non-zero exits (3 gate, 5 API, 6 read-back). Giving the inline blocks machine-readable outcomes is a lane-driver change, not a #943 fix. - The three blocks are byte-identical with no drift gate. `scripts/check-cross-plugin-source-drift.sh` structurally cannot see this cluster — `skip_basenames` excludes `SKILL.md`, and the third copy sits at a different path-within-plugin. Registering the cluster is worth its own change. ## Related - Refs #502 — the observability fail-open this defect instantiates. - Refs #1841 — keys the telemetry marker per lane instance in the same blocks; merged in here. - `plugins/claude-ops/skills/lanes/scripts/telemetry-upsert.sh` — the wrapper whose gate and read-back these inline blocks replicate, and whose limits they inherit. - `docs/conventions/loop-lane/README.md` — the loop-lane convention updated alongside. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01FVoZoMYXqf8ZVbQYixPVPW --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two textual conflicts, both mechanical: main shipped work-items 0.31.0 and 0.31.1 while this branch held 0.30.4, so the manifest takes 0.31.2 and this branch's entry is renumbered and stacked above main's shipped entries, which are untouched. Semantic check on the auto-merged side: main (#1841, #1850) moved work-loop's telemetry upsert body into reference/telemetry-upsert.md and made the sentinel marker lane-instance-suffixed. Both surfaces this branch cites still hold -- the "Telemetry and durable loop state" heading still states that the telemetry home is resolved from launch config before the default title, and the sentinel this branch matches on is written with an ellipsis after `marker=`, so the instance suffix does not falsify it. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
<!-- CURSOR_AGENT_PR_BODY_BEGIN --> Fixes #1290 ## Summary Two concurrent attended sessions on one repository could both surface and mutate the same row because `/work-items:attend-queue` had no claim protocol while `/work-items:work` already used the seam assignee + lease (`exit 7` → advance). Documents the same seam claim protocol `work` uses, scoped to attend-queue row disposition: claim before mutate, release before flip to autonomous-eligible, session-start reclaim, binding routing. Bumps `work-items` to **0.35.16**. ## Test plan - [x] Eval #5 covers concurrent-session exit-7 skip behavior - [x] Single-session run needs no new required argument ## Related - #1290 - Unblocked by #1295 / #1841 <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-8279724f-c3b6-4e4f-be24-3dcf28782ae1?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-8279724f-c3b6-4e4f-be24-3dcf28782ae1&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
Fixes #1291 ## Summary Gives `/work-items:work-loop` a minimal invocation argument surface mirroring babysit-loop where appropriate: `[<owner/repo>] [--drain] [--shard <i>/<n>] [--ordering oldest-first|newest-first] [--instance <id>] [--scope <label>]` Rejects `--merge` / babysit tier / cap knobs fail-closed. Progressive disclosure moves stop-mode exit semantics into `reference/mode-standing.md` and `reference/mode-drain.md`. Bumps `work-items` to **0.35.17** (0.35.16 reserved for #1290 attend-queue claim). ## Test plan - [x] Evals 6–8 cover shard/ordering/rejected-merge, drain vs standing, scope partitioning - [x] Eval 3 updated to pass `--drain` where drain-terminal stop is expected ## Related - #1291 - Unblocked by #1295 / #1841 --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
Fixes #1295
Implements the operator decision recorded on the issue: concurrent multi-lane operation against one
repository is supported, and writer identity is partitioned in the sentinel marker, not in the
issue title.
The defect
Every lane built its telemetry sentinel from a fixed marker naming the lane type
(
MARKER="work-items:work-loop"). Two instances of one lane on one repository therefore resolvedthe same comment on the same telemetry issue and overwrote it last-writer-wins. The creation-race
reconcile already in the upsert does not help — it converges duplicate comments, not conflicting
state written by two live lanes, and the issue thread carries a timestamped live reproduction of
exactly that post-creation overwrite race.
item_cap,clean_streak, andrate_limit_latchsilently stopped reflecting either lane'sexperience.
first_drain_completeis the serious one: it ends the first-drain C3 ratification gate,so one machine finishing a drain ended the earn-trust period for every machine — a safety property
failing open with no human ratification.
The fix
The marker gains a lane-instance suffix, the lane-type marker becoming its prefix
(
work-items:work-loop@<instance>). Each instance creates, reads, and edits exactly one comment nosibling can match, so every counter in the block becomes per-instance — the only semantically
correct reading, since each measures that lane's observed experience.
The id is
${user_config.lane_instance}(the repo's sanctioned skill-body channel), defaulting tothe sanitized lowercased hostname, validated
^[a-z0-9][a-z0-9-]{0,31}$inside each lane's ownexecutable block and rejected rather than sanitized-and-continued — it is operator-supplied text
interpolated into a shell string and a
jqprogram, and a guard that lives only in prose does notrun. The hostname fallback is a default, not a sanitizer: it passes the same gate.
Partitioning is correct only while ids are distinct, so collision is detected rather than assumed.
The state block gains
lane_instance, a per-sessionwriter_nonce, a per-cycleheartbeat_at, andpaused_until. A differing nonce over a stale block is the ordinary restart adoption; over afresh block it means another live lane holds the id — the lane writes nothing, escalates, and
stops cleanly. The check runs before any write, so a duplicate id degrades to a stopped lane rather
than a clobbered
first_drain_complete. The 2h staleness window is twice the one-hourScheduleWakeupceiling, so maximum idle backoff never reads as death;paused_untilkeeps arate-limit pause from doing so either.
Acceptance criteria
full sentinel.
first_drain_completecannot be set by another lane's progress. It lives in a block noother instance can write. Earn-trust is re-earned per instance; item-level ratifications still
travel with the item, so only the blanket period-end flag resets.
Lane telemetry: <lane>title does notchange at all — that is why the marker was chosen as the seam over the title. Mechanically
checkable:
git diff origin/main | grep 'Lane telemetry'returns only prose about the contract,never a changed title literal. The drain-exit snapshot, the intake sweep, and the attention view
all still match.
Two consumers moved with it
telemetry-upsert.shadmits@in the marker charset and in both fallback lookaround classes— without the latter,
lane:xmatches insidelane:x@laptop-aand would adopt that instance'scomment, the same superstring rule the existing
lane:triage/lane:triage-oldguard enforces.restart-consumer.shmatched a boundtelemetry.markerby exact equality. Once lanes carrythe suffix, no bound lane's comment would match — the consumer would report
no-stateforever andrestart nothing, the worst failure shape for an unattended relaunch trigger, since a silent no-op
is indistinguishable from a healthy quiet tick. A bound marker now names a lane type across every
instance, with a new optional
telemetry.instancekey to pin one; the scan also no longer lets aquiet sibling instance mask a later instance's live restart request.
Migration
A deliberate reset. No pre-existing comment matches an instance's new sentinel — neither the legacy
un-suffixed markers nor the improvised
work-items:telemetry lane=… instance=…comments some lanesbegan posting in practice — so the first cycle posts a fresh block from defaults, including
first_drain_complete:false. That fails closed and is intended; it produces one burst ofratification queue comments on the next drain. A lane never adopts, edits, or tombstones the legacy
comment: its marker names no writer, so no instance can prove it owns it, and adopting it would
reintroduce the very clobber this removes. Retiring it is an operator action.
claude-ops:morning-briefkeeps working, cosmetically degraded — N instances render as N rows alllabelled with the same lane. The instance is emitted on its own
instance:line (that reader'slane:capture is[a-z0-9_-]+and would truncate a suffix appended there). Rendering per-instancerows is a separate cosmetic follow-up in
claude-ops.Verification
telemetry-upsert.test.sh— 92 cases pass, including three new ones: a lane-type marker must notadopt an instance's comment, an instance marker must not adopt a longer instance marker, and a
suffixed marker validates at all.
restart-consumer.test.sh— 141 cases pass, including three new ones: a bare bound marker matchesan instance-suffixed comment, a superstring lane type is not adopted, and a quiet sibling instance
does not mask a later request.
check-changed-skills.sh,check-changelog-parity.sh --check/--check-order, markdownlint,shellcheck,
bash -n— all clean.babysit-loop/SKILL.mdwas already 495/500 lines onmain, so its collision-check branches went toreference/telemetry-upsert.md, which already owns every other mechanic of the same upsert.Related
whether durable state lives on an Issue comment at all. Whatever surface wins carries the same
per-writer sentinel.
should be threaded through its argument surface rather than deriving a second one.
compatible with this partition.
overlap with this diff.
Overlap note for concurrent branches. Sibling work is in flight on
work-itemstriage (#1739)and
source-controlbabysit skills (#1660 / #1641 / #1525). This branch is based onorigin/mainonly. It touches
plugins/source-control/skills/babysit-loop/SKILL.md(telemetry section, ~line359) and its
reference/telemetry-upsert.md, plusplugins/work-items/skills/attend-queue/SKILL.md(telemetry section) — a
babysit-loopbranch editing the telemetry section, or any branch touchingthe three plugin manifests'
userConfigblocks or CHANGELOGs, will conflict textually.Do not merge without review
This is a C4 structural change to a cross-plugin convention contract (loop-lane convention
7.0.0), so it stays human-merge unconditionally.