Repository navigation
register_env_triggers: optional await of start-of-run triggers (FD-3998) - #227
Merged
earakely-scale merged 6 commits intoOct 16, 2026
Conversation
The step returns when its triggers are armed, so a task whose world is seeded by absolute-time triggers had to carry a run_code warm-up script that polled the gateway until they fired. `await_fired` moves that guard into the step: after registering, it polls the triggers extension's `state` method until every named trigger reads `await_status` (default `fired`), stops early on `failed` or `cancelled`, and raises with the per-trigger statuses and the gateway's failure events for them when the deadline passes. The audit entry gains `awaited`. `preflight` and `execute` refuse an awaited id the step does not register, and a gateway whose card lacks `state` is refused before any request. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…stration before waiting, name the wait settings Each trigger-state read and each retry backoff is now cut to what is left of `await_timeout_seconds`, so a stalled gateway cannot hold the step past its deadline. The registration audit entry is appended before the wait, with `awaited` carrying the last statuses seen, so a failed wait no longer hides a registration that later steps look up. The poll interval, read timeout, attempt count, backoff and the number of failure events quoted are named module constants. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…stration entry Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A state read now starts only with a full minimum read window left before the deadline and is cut to that remainder, so neither a slow read nor a late reply can carry the step past `await_timeout_seconds`; a budget shorter than one read fails at once with the statuses last seen. The failure message is built in one place. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Collaborator
Author
|
@greptileai review |
`await_status` is gone: the warm-up only ever waits for triggers to fire, and a configurable target brought a validation branch, a computed terminal set and odd cases such as awaiting `failed`. The target and the terminal statuses are constants; the messages say "fire" instead of quoting a status. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Collaborator
Author
|
@greptileai review |
… in execute The state read no longer has its own three-attempt retry with backoff; a failed read is retried on the next poll under the step's single deadline, and the last failure is reported if the deadline passes. execute raises preflight's own problems instead of a second copy of the unknown-id message, and await_timeout_seconds rejects booleans. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
earakely-scale
deleted the
edgararakelyan/fd-3998-register_env_triggers-optional-await-of-start-of-run-time
branch
October 16, 2026 05:27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
register_env_triggersgains two optional fields:await_fired(trigger ids) andawait_timeout_seconds(default 120). After theregistercall the step polls the triggers extension'sstatemethod until every named trigger has fired, stops early onfailedorcancelled, and raises with the per-trigger statuses and the gateway'sfailed/eval_error/action_failedevents for those ids when the deadline passes. The registration audit entry gainsawaited: {id: status}, so judges and the explorer can see it. The await has one deadline: each state read is cut to the time left and never starts inside the last second, a read that fails is retried on the next poll, and if the deadline passes the last read failure is reported with the statuses last seen. Nothing changes for steps that do not set the field; stored tasks load unchanged.Guards:
preflightnames an awaited id the step does not register andexecuteraises preflight's own problems before touching the context, and a gateway whose card does not offerstateis refused before any request, in the same style as the feature check.Why
A task whose world is seeded by absolute-time triggers (an owner's invitation at 17:00:02, a supervisor DM at 17:00:04) must not start its prompt step before those records exist. Until now the only way to guarantee that was a per-task
run_codewarm-up script that polled/triggers/state; every Cooperation Bench task carries a copy, and an author can forget it. The gateway already fires time triggers on its own; this moves the wait into the step that registers them.Out of scope: staged clock advancement for worlds whose events are spread over virtual time. The step does not touch the clock.
Tests
tst/unit/task_step/test_register_env_triggers.py, 14 new): round trip with and without the fields, construction validation (including a boolean timeout), preflight naming an unregistered id, refusal before any request for an unregistered id and for a card withoutstate, return once each id reads fired, no state read when not awaiting, timeout message with statuses and failure events, early stop onfailed, a missing id waits to the deadline, a budget shorter than one read fails without reading, slow reads cannot outlive the budget, a failed state read is retried on the next poll, a persistently failing read is reported with its cause at the deadline.tst/integration/task_step/test_register_env_triggers_local.py, local gateway on Docker): two time triggers at +2 and +3 virtual minutes at sixty-to-one fire and the step returns withawaitedafter about three seconds; a trigger eight hours out times out with the expected message.Chaos
Harness kept out of the tree (
~/sp-scratch/chaos3998/on Edgar's machine). Each run deploys the in-repo items env on local compose with its gateway, arms the virtual clock at sixty-to-one, and registers one-shot time triggers against the real trigger engine.Scenarios: S1 one to three triggers 1–4 virtual minutes out, the step must return with every id fired within about the latest mark; S2 a trigger eight hours out with a 2–4 s budget, must time out naming the armed status; S3 a trigger whose action is a bad tool call, must stop early with the gateway's
action_failedevent; S4 clock cleared, must time out; S5 a proxy injecting 503s, 2–4 s delays and connection resets on ~45% of state reads, must still return or fail only with the read error; S6 four steps awaiting their own ids concurrently on one env; S7 an awaited id the step does not register, refused before any request; S8 a recurring trigger awaited as fired, times out by design. Soak: S1, S6 and S5 in rotation. Disruptions: SIGKILL of the process eight seconds into a 40 s await, andsystemctl restart dockerfive seconds into a 60 s await, each followed by a clean rerun and a leftover check.What the disruptions showed: after SIGKILL the only things left behind are the deploy's own compose containers, which the step cannot be responsible for; after a Docker restart, or a killed gateway container, the gateway does not come back on local compose. On a2b9420 the await gave up after its three bounded reads (
could not read /triggers/state … All connection attempts failed) about 15 s in; on ebb27bf a failed read is retried on each poll instead, so the step ran to its deadline and reported the last failure with the statuses last seen (could not read /triggers/state within the 90s await budget: All connection attempts failed; statuses {'s1-0': 'armed'}, 86 s after the kill), never hanging past the budget. S5 across all runs: every trial returned with the trigger fired; no trial needed the read-error path. The new integration test also passed on all three VMs (139–149 s). Three unit-tier failures on the VMs are environmental (unzipabsent; the CLI-builder tests run generated CLIs with the system Python) and unrelated; the tier is green locally.🤖 Generated with Claude Code


Confidence Score: 5/5
The latest changes introduce no new blocking issues.Summary
RegisterEnvTriggersStepcan wait for named triggers before allowing later steps to start.No new actionable issues were found in the changes since the previous review. Tests were inspected, not run.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Check settings and gateway support] --> B[Register triggers] B --> C[Record registration] C --> D{Await requested?} D -- No --> E[Return] D -- Yes --> F{Enough time remains?} F -- No --> G[Raise with last statuses and failure details] F -- Yes --> H[Read trigger state] H -- Read fails --> I[Keep last state and read error] I --> J[Wait before next poll] H -- Read succeeds --> K[Update recorded statuses] K --> L{All fired?} L -- Yes --> E L -- No --> M{Failed or cancelled?} M -- Yes --> G M -- No --> J J --> FReviews (5) · Last reviewed commit: "register_env_triggers: one deadline for ..." · Reviewed by Greptile