Merge fix/compose-pass-bounded: the composition pass never waits on marking haves

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 22:35:09 +01:00
5 changed files with 229 additions and 3 deletions
+19 -1
View File
@@ -191,6 +191,8 @@ type Composer struct {
wait time.Duration
// asked are the compose verb's asks (ComposeNow), each answered once the pass it asks is made.
asked chan chan Pass
// passing is set while a pass is made.
passing atomic.Bool
// last is how the last pass went, under mu.
last Pass
// watching says the composer's loop runs (Watch), so an ask of a pass can be answered.
@@ -240,6 +242,9 @@ type Pass struct {
Again time.Time `json:"again,omitempty"`
// Earlier says the answer is the last pass, made less than askedWithin ago, and no new one was made for the ask.
Earlier bool `json:"earlier,omitempty"`
// Queued says a pass was running when the ask came: one more is made when it ends, and this answer is not its result.
// It says what the running pass waits on and since when.
Queued string `json:"queued,omitempty"`
}
// LastHeld is what the last pass held, each module with why; nil before a pass or when it held none.
@@ -331,6 +336,16 @@ func (c *Composer) ComposeNow(ctx context.Context) (Pass, error) {
return last, nil
}
reply := make(chan Pass, 1)
if c.passing.Load() {
// A pass runs: the ask joins the one made when it ends (all asks of the time share it), and is answered now, never
// held for the pass (issue 1965: the verb gave no answer for minutes while a pass waited on the bus).
select {
case c.asked <- reply:
default: // eight asks already wait for that pass
}
last.Queued = "a pass is running and waits on " + c.waitingOn() + "; one more is made when it ends"
return last, nil
}
select {
case c.asked <- reply:
case <-ctx.Done():
@@ -539,7 +554,10 @@ func (c *Composer) Watch(ctx context.Context, _ string) (<-chan agent.Update, er
continue
}
dirty = false
for _, u := range c.compose(ctx) {
c.passing.Store(true)
updates := c.compose(ctx)
c.passing.Store(false)
for _, u := range updates {
select {
case out <- u:
case <-ctx.Done():