You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Claudinite: refresh the vendored mount to canon 7e89c0c (#87)
Out-of-band refresh, pushed from a canon session, because this repo could not
perform it itself.
Its scheduler has been running green and skipping baselining every night with
"no vendored mount (no stamp)" — on a repo whose .claudinite-checks.json carries
one. The vendored `loadConfig` validated `claudinite` as a legal settings key and
then dropped it from the object it returned, so the `stamp` signal read undefined
and baselining took the branch meant for the canon and pre-adoption repos.
That made the fix unreachable by the normal route: baselining is what updates the
mount, and the bug was in the mount. Hence this manual converge.
Carried by this refresh (canon PRs #511, #516, #518):
- loadConfig keeps `claudinite` and `maintenance`, so the stamp is readable again
- the executor resolves its dispatch from the CCR trigger, not just the Actions
payload, and no longer guesses an issue when it cannot identify one
- FORCE_TASKS: a manual scheduler run can force a task past its slot gate
Mount converged whole-set and the wiring re-converged from the vendored stub
(the scheduler workflow gains the `overrides` input).
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: .claudinite/shared/engine/scheduler/executor.md
+92-42Lines changed: 92 additions & 42 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,15 @@
1
1
# Claudinite executor
2
2
3
+
> **Before anything else — name your one issue.** This session runs exactly one
4
+
> dispatch. Step 1 resolves it **in code** from the trigger this session carries
5
+
> and validates it; state the number it prints before you touch anything.
6
+
> If you cannot name exactly one, **run nothing and end the session** — the
7
+
> scheduler re-arms an unrun dispatch on its next hourly pass, so stopping costs a
8
+
> delay while guessing costs duplicated work. **There is no fallback**: never
9
+
> select a dispatch by listing the queue and taking one. Never process a second
10
+
> issue in one session, under any circumstance — including when a listing shows
11
+
> several waiting. Those are other sessions' dispatches, already running.
12
+
3
13
You are the per-repo **executor** — you run the scheduled **tasks** dispatched to
4
14
this repo (per-project-scheduling DESIGN §5). A routine wired to a dispatch label
5
15
event started this session: the scheduler Action evaluated a task's precondition,
@@ -46,46 +56,83 @@ so use `engine/scheduler/`.
46
56
47
57
## Procedure
48
58
49
-
1.**Your work list is exactly one issue: the one whose labeling triggered this
50
-
session.** Run it and nothing else. Do **not** list, claim, or process any
51
-
other open issue — not under your ready label, and not under the other one.
52
-
53
-
This is load-bearing, not a style preference. One scheduler run files every due
54
-
task's dispatch issue within a couple of seconds, each already carrying its
55
-
ready label, so **one run emits one label event per issue and starts one session
56
-
per event**. An executor that also swept its siblings had every one of those
57
-
sessions build the same N-issue work list and race over it — and the claim in
58
-
step 3 could not prevent it, because all the sessions read the list before any
59
-
of them claimed. That produced the same dispatch executed two and three times
60
-
over: duplicate tracker issues, duplicate bug reports, duplicate PRs making the
61
-
same changes. One session, one issue, and the concurrency is safe by
62
-
construction.
59
+
1.**Resolve and validate your one dispatch — in code, first thing.**
60
+
61
+
```bash
62
+
node <engine>/scheduler/resolve-dispatch.mjs self # `fleet` if your trigger is ready-for-agent-fleet
63
+
```
64
+
65
+
(`<engine>` per Engine command paths above.) It finds the `issues.labeled`
66
+
trigger that started this session — from **either** transport, see below —
67
+
and asserts in code, before any judgment of yours, that the issue body names a
68
+
legal task path, the file exists at HEAD, its pack is declared, and its
69
+
`task.mjs` sibling parses to a valid declaration. It makes no GitHub calls of
70
+
its own.
71
+
72
+
**Your trigger arrives one of two ways, and the shell reads both:**
73
+
74
+
-**GitHub Actions** writes the whole webhook payload to `$GITHUB_EVENT_PATH`
75
+
— issue number, label, and body all in one file, so this resolves in a
76
+
single command.
77
+
-**Claude Code on the web (CCR)** writes no payload file. It delivers the
78
+
same trigger as environment variables — `CCR_TRIGGER_SOURCE`,
79
+
`CCR_TRIGGER_EVENT`, `CCR_TRIGGER_REPO`, `CCR_TRIGGER_ISSUE_NUMBER` — which
80
+
**name your issue but carry neither its labels nor its body**. So this
81
+
resolves in two commands: the shell exits `13` telling you the number, you
82
+
fetch **that one issue** over MCP, and you re-run with what you fetched.
83
+
84
+
**Act on its exit code — that is the interface**, not the prose it prints:
85
+
86
+
| exit | verdict | what you do |
87
+
| --- | --- | --- |
88
+
|`0`| valid dispatch, your scope | Go to step 2. The printed block is your brief: issue, task path, pack, task, model, outcome ceiling, `executionTimeout`. |
89
+
|`13`| CCR trigger, issue named, body needed | Fetch **the printed issue and only it** over MCP — its body and its current labels — write the body verbatim to a file, and re-run: `node <engine>/scheduler/resolve-dispatch.mjs self --issue-body-file <path> --issue-labels <csv>`. Then act on *that* run's exit code. |
90
+
|`10`| invalid dispatch | It never runs. Comment the printed `reason`, remove the ready label, add `needs-human`, end the session. |
91
+
|`11`| not yours | The trigger label is the *other* executor's, no ready label at all, or the issue no longer carries a ready label (another session already claimed it). **Stop**: change nothing, comment nothing, end the session. |
92
+
|`12`| no trigger at all |**Stop the session immediately.** See below. |
93
+
|`2`, `1`| bad invocation, internal fault | Comment what you saw, add `needs-human` if you know the issue, end the session. Do not proceed on a guess. |
94
+
95
+
**State the issue number before you act**, so everything after this has one
96
+
unambiguous subject. Run that issue and nothing else: do **not** list, claim,
97
+
or process any other open issue — not under your ready label, and not under
98
+
the other one.
99
+
100
+
**Why one issue, and never a sweep.** One scheduler run files every due task's
101
+
dispatch within a couple of seconds, each already carrying its ready label, so
102
+
**one run emits one label event per issue and starts one session per event**.
103
+
An executor that also swept its siblings had every one of those sessions build
104
+
the same N-issue work list and race over it — and the step-2 claim cannot
105
+
prevent that, because all the sessions read the list before any of them claim.
106
+
That ran the same dispatch two and three times over: duplicate tracker issues,
107
+
duplicate bug reports, duplicate PRs making the same changes. One session, one
108
+
issue, and the concurrency is safe by construction.
63
109
64
110
A dispatch whose label event never landed is **not yours to rescue**. The
65
111
scheduler re-arms it in code on its next hourly run (`dispatch.mjs`
66
112
`rearmDispatchIssues`) and escalates it to `needs-human` if it stays unrun past
67
113
~2 of its scheduling periods. Leave it alone.
68
114
69
-
*If, and only if, you genuinely cannot determine which issue triggered this
70
-
session*, take the **single oldest** open issue under your ready label —
71
-
`ready-for-agent` for a self session, `ready-for-agent-fleet` for a fleet
72
-
session — and run that one alone. Never take more than one, and never take an
73
-
issue under the other ready label; that is the other executor's to run.
74
-
75
-
2.**Validate deterministically before any judgment.** Run
76
-
`node <engine>/scheduler/validate-dispatch.mjs <issue-number>` (`<engine>` is
77
-
`.claudinite/shared/engine` in a consumer, `engine` in the canon — see Engine
78
-
command paths above). It checks in code that the first line is a legal task path
79
-
(`packs/<pack>/tasks/<task>/task.md`, optionally under a
80
-
`.claudinite/shared/` or `.claudinite/local/` prefix — the canon's own packs
81
-
are root-relative, a consumer's are under its mount), the file exists at HEAD,
82
-
its pack is declared, and its `task.mjs` sibling parses to a valid declaration;
83
-
it prints the resolved **model**, **outcome** ceiling, and the task's
84
-
**executionTimeout** (seconds).
85
-
- Invalid → comment naming what failed, remove the ready label, add
86
-
`needs-human`, and end the session. A forged or mangled dispatch never runs.
87
-
88
-
3.**Claim the issue — read, swap, then re-read to confirm you won.** The same
115
+
**Exit 12 — stop the session. There is no fallback.** The trigger is how you
116
+
know which issue is yours. Without one you do not have a dispatch to run, and
117
+
**selecting one yourself is forbidden**: do not list the queue, do not take the
118
+
oldest, do not take *any*. Run nothing, change nothing, comment nothing, end
119
+
the session.
120
+
121
+
This is not caution for its own sake — a session that picks its own issue is
122
+
the duplicate-execution bug reached from the other direction. One scheduler run
123
+
files every due dispatch seconds apart, so **every** session that cannot name
124
+
its trigger builds the *same* list and races the others over it; the step-2
125
+
claim cannot save them, because they all read the list before any of them
126
+
claims. That is exactly how #772 was claimed twice, one second apart, on
127
+
2026-07-28. Stopping is cheap: the scheduler re-arms an unrun dispatch on its
128
+
next hourly pass and escalates it to `needs-human` if it stays unrun. Guessing
129
+
is not.
130
+
131
+
An exit 12 in a session that *was* triggered by a label event means the trigger
132
+
did not reach the shell — a real defect worth a human noticing. Say so plainly
133
+
in your final message rather than working around it.
134
+
135
+
2.**Claim the issue — read, swap, then re-read to confirm you won.** The same
89
136
issue can still be labeled twice (a re-arm that overlapped a slow session, a
90
137
human re-applying the label), so the claim is a lease you must verify, not a
91
138
write you may assume. GitHub has no compare-and-swap on labels; these three
@@ -102,22 +149,25 @@ so use `engine/scheduler/`.
102
149
session without dispatching** — do not remove `agent-running` (the winner
103
150
is running behind it) and do not converge the issue.
104
151
105
-
Only past step 3.3 may you dispatch anything.
152
+
Only past step 2.3 may you dispatch anything.
106
153
107
-
4.**Dispatch a subagent at the declared model.** The subagent reads the
154
+
3.**Dispatch a subagent at the declared model.** The subagent reads the
108
155
task file (`task.md`) and follows it exactly. The issue's **Context** section
109
156
is **binding scope** — never re-decide or widen it: if the precondition ruled
110
157
something out, it stays out. **Give the subagent its run bound**: tell it
111
158
plainly *"you have N minutes (this task's `executionTimeout`); if you exceed
112
159
it, stop, comment what's done, and converge this issue to `needs-human` rather
113
160
than pressing on."* This is best-effort — there is no platform wall-clock kill
114
161
for this session (agent-preprocessing DESIGN §6) — so the value comes from the
115
-
**task declaration** printed by validate-dispatch, never from the issue body.
116
-
117
-
5.**Verify the outcome in code, then converge — then stop.** Determine what the
118
-
run did to pull requests and check it against the ceiling with
119
-
`verify-outcome.mjs` — a `none` task that opened a PR, or an `open-pr` task
120
-
that merged one, **fails the run**. Then:
162
+
**task declaration** printed by step 1, never from the issue body.
163
+
164
+
4.**Verify the outcome in code, then converge — then stop.** The declared
165
+
`expected_outcome` is a **ceiling, not a target**: it is the most a task may do,
166
+
and **"no change" is always legal** — a run that found nothing worth changing is
167
+
a success, never a reason to manufacture work. Determine what the run did to
168
+
pull requests and check it against that ceiling with `verify-outcome.mjs` — a
169
+
`none` task that opened a PR, or an `open-pr` task that merged one, **fails the
170
+
run**. Then:
121
171
- Success within ceiling → comment the result and **close** the issue.
122
172
- Failure (task failed, or ceiling violated) → comment naming what failed,
123
173
remove `agent-running`, add `needs-human`. Do not close.
0 commit comments