A forbidden word reaches the run as a message, clean commits are pushed, and the wait names the word #134

Owner

Tracker: #3722

Step 2 of plan #3694. Fixes #3690: a forbidden word in what a run would publish no longer kills the run.

What it delivers

  • A claim's instructions gain a ## Forbidden words part listing every forbidden word in order, saying that sak publishes no commit message and no pull-request.md that holds one, ignoring case. With no forbidden words the part is left out.
  • The runner reads what the run would publish (the new commits' messages and pull-request.md) whenever pull-request.md changes, whether or not the PR is open, and at each turn's end before the turn settles. A hit no longer sends SIGTERM.
  • On a hit the session has not been told of, the runner posts a message naming the word and pull-request.md or the commit to the stream's inbox, and hands it to the session. A turn that ends while such a hit stands does not settle: the message is its next input.
  • A turn that settles while a hit the session was told of still stands ends the run failed · forbidden_words at once, not at the idle cap.
  • At the run's end, commits whose messages are clean are pushed even when the PR text holds a word; the text is withheld and no PR is opened or written. A commit message holding a word pushes nothing.
  • The stream's close carries word and in (pull-request.md, or the commit's id), and the flow run's needs_you event carries them.
  • The card's cause line reads "the PR text holds the forbidden word " or "commit holds the forbidden word ", a line of the existing timeline entry.
  • docs/api.md and runner/README.md describe this. runner/README.md Repository says when the runner reads what the run would publish as events arrive: once a second at most, when the PR text changed, or when the agent has written it and the branch has no open PR. It says a hit is the oldest new commit whose message holds a forbidden word, with the first of the words, in sak's order, that it holds; else, when the PR text holds one, the first of them it holds.

Master already holds flow run 36's merge (#3687), and the plan's branch holds that merge (4bd0747), so no merge was needed.

Regressions, failing at the base and passing at the head

Each was run from a git archive arm with the gate cache's sx: the head arm at 4a70ceb, and the base arm at 762efb1 (the plan's branch, this step's base) carrying 4a70ceb's tests/runner_core.sx and the stub stream-json harness.

tests/runner_core.sx, check_repository:

check base 762efb1 head 4a70ceb
a claim's instructions list the forbidden words, saying sak publishes no commit message and no pull-request.md that holds one FAIL ok
a run whose PR text holds a forbidden word is handed a message naming the word and pull-request.md, rewrites the text and ends done FAIL: the run finished failed · forbidden_words the second it started ok: finished done
its commit is pushed and its PR carries the clean text FAIL: nothing pushed ok
the message is on the run's stream as an inbox message, delivered FAIL ok
a run that ignores the message ends failed with cause forbidden_words within a second of its next turn's end FAIL: killed before any message ok
its clean commit is pushed, no PR is opened, and its close names the word and pull-request.md FAIL: nothing pushed, no word/in ok
a commit message holding a forbidden word ends the run failed, the message and the close naming the word and the commit FAIL ok
and publishes nothing: no branch, no PR, no pushed head ok ok

tests/runner_core.sx, check_flow_runs:

check base head
a step's run failed with cause forbidden_words goes to Needs you, the wait naming the word and pull-request.md FAIL: the needs_you event has no word or in ok

The head arm ended RUNNER CORE OK (396 checks). The base arm ended RUNNER CORE FAIL (8 checks), the eight rows marked FAIL above, with 388 ok. An earlier base arm, run under load beside another gate, also failed "a run whose stub harness waits silently on a gate longer than the idle timeout is not ended idle_timeout". That check's 500 ms idle cap is timing-bound, this change touches neither gates nor the stub's script path, and the check passed in the base rerun and at the head.

tests/runner_core.sx, check_happy_path: "with no forbidden words, the instructions carry no part for them" passes at both, because the base adds no part at all.

web/src/lib/flow-runs.test.ts "names the forbidden word and the PR text or the commit that holds it", run with vitest in a scratch copy of web/: at the head it passes (10 passed). With the base's flow-runs.ts it fails with Expected: "the PR text holds the forbidden word zebracorn" / Received: "a commit or the PR text holds a forbidden word".

Golden set: none. No golden shows a forbidden_words wait.

Review

Review run 568 found the README's Repository sentence and the comment on poll in runner/repository.sx describing a read the code does not make: while the branch has no open PR, the runner reads only once the agent has written its PR text, so a commit message holding a word made before pull-request.md exists is found at the turn's end. Its "first word" wording also read as the first word in the message rather than the first of sak's words. b533de6 rewrites both to state the read the code makes, and the same wording in the comment on commit_hit. It changes no code.

Review run 583 found that sentence still naming the oldest new commit, so that a word in a later commit's message read as no hit, while commit_hit names the oldest new commit whose message holds a word. The last commit rewrites the README sentence to say so, and the comment on found in runner/repository.sx the same way. It changes no code either, so the regression results above, taken at 4a70ceb, hold at the head.

Gates

At 4a70ceb:

  • fast: gate run 309, passed; the review's fast, gate run 315, passed.
  • full: gate run 314, failed only at the runner Playwright config's global setup (sak serve never answered on http://127.0.0.1:18988). Every earlier step passed, the main Playwright config with 740 passed and 1 skipped. Another process held port 18988 on s32 then, and it has since been ended. Run by hand from a git archive arm with PORT moved to 18978, the runner config passed 10 of 10.

At b533de6:

  • full: gate run 320, passed. Every step passed: the sak and runner builds, npm run build (402 vitest tests), the libc grep, every tests/*.sx file, release with tests/step9_release.sx, the deploy contract, the main Playwright config (740 passed, 1 skipped) and the runner Playwright config (10 passed).
  • the review's fast, gate run 321, passed.

At the head 1786318:

  • full: gate run 325, passed. Every step passed: the sak and runner builds, npm run build (402 vitest tests), the libc grep, every tests/*.sx file, release with tests/step9_release.sx, the deploy contract, the main Playwright config (740 passed, 1 skipped) and the runner Playwright config (10 passed).
Tracker: [#3722](https://sak.swipelab.com/tasks/3722) Step 2 of plan #3694. Fixes [#3690](https://sak.swipelab.com/tasks/3690): a forbidden word in what a run would publish no longer kills the run. ## What it delivers - A claim's `instructions` gain a `## Forbidden words` part listing every forbidden word in order, saying that sak publishes no commit message and no `pull-request.md` that holds one, ignoring case. With no forbidden words the part is left out. - The runner reads what the run would publish (the new commits' messages and `pull-request.md`) whenever `pull-request.md` changes, whether or not the PR is open, and at each turn's end before the turn settles. A hit no longer sends SIGTERM. - On a hit the session has not been told of, the runner posts a message naming the word and `pull-request.md` or the commit to the stream's inbox, and hands it to the session. A turn that ends while such a hit stands does not settle: the message is its next input. - A turn that settles while a hit the session was told of still stands ends the run `failed · forbidden_words` at once, not at the idle cap. - At the run's end, commits whose messages are clean are pushed even when the PR text holds a word; the text is withheld and no PR is opened or written. A commit message holding a word pushes nothing. - The stream's close carries `word` and `in` (`pull-request.md`, or the commit's id), and the flow run's `needs_you` event carries them. - The card's cause line reads "the PR text holds the forbidden word <word>" or "commit <short id> holds the forbidden word <word>", a line of the existing timeline entry. - `docs/api.md` and `runner/README.md` describe this. `runner/README.md` Repository says when the runner reads what the run would publish as events arrive: once a second at most, when the PR text changed, or when the agent has written it and the branch has no open PR. It says a hit is the oldest new commit whose message holds a forbidden word, with the first of the words, in sak's order, that it holds; else, when the PR text holds one, the first of them it holds. Master already holds flow run 36's merge (#3687), and the plan's branch holds that merge (4bd0747), so no merge was needed. ## Regressions, failing at the base and passing at the head Each was run from a `git archive` arm with the gate cache's sx: the head arm at 4a70ceb, and the base arm at 762efb1 (the plan's branch, this step's base) carrying 4a70ceb's `tests/runner_core.sx` and the stub stream-json harness. `tests/runner_core.sx`, `check_repository`: | check | base 762efb1 | head 4a70ceb | | --- | --- | --- | | a claim's instructions list the forbidden words, saying sak publishes no commit message and no pull-request.md that holds one | FAIL | ok | | a run whose PR text holds a forbidden word is handed a message naming the word and pull-request.md, rewrites the text and ends done | FAIL: the run finished `failed · forbidden_words` the second it started | ok: finished `done` | | its commit is pushed and its PR carries the clean text | FAIL: nothing pushed | ok | | the message is on the run's stream as an inbox message, delivered | FAIL | ok | | a run that ignores the message ends failed with cause forbidden_words within a second of its next turn's end | FAIL: killed before any message | ok | | its clean commit is pushed, no PR is opened, and its close names the word and pull-request.md | FAIL: nothing pushed, no `word`/`in` | ok | | a commit message holding a forbidden word ends the run failed, the message and the close naming the word and the commit | FAIL | ok | | and publishes nothing: no branch, no PR, no pushed head | ok | ok | `tests/runner_core.sx`, `check_flow_runs`: | check | base | head | | --- | --- | --- | | a step's run failed with cause forbidden_words goes to Needs you, the wait naming the word and pull-request.md | FAIL: the `needs_you` event has no `word` or `in` | ok | The head arm ended `RUNNER CORE OK` (396 checks). The base arm ended `RUNNER CORE FAIL (8 checks)`, the eight rows marked FAIL above, with 388 ok. An earlier base arm, run under load beside another gate, also failed "a run whose stub harness waits silently on a gate longer than the idle timeout is not ended idle_timeout". That check's 500 ms idle cap is timing-bound, this change touches neither gates nor the stub's script path, and the check passed in the base rerun and at the head. `tests/runner_core.sx`, `check_happy_path`: "with no forbidden words, the instructions carry no part for them" passes at both, because the base adds no part at all. `web/src/lib/flow-runs.test.ts` "names the forbidden word and the PR text or the commit that holds it", run with vitest in a scratch copy of `web/`: at the head it passes (10 passed). With the base's `flow-runs.ts` it fails with `Expected: "the PR text holds the forbidden word zebracorn"` / `Received: "a commit or the PR text holds a forbidden word"`. Golden set: none. No golden shows a `forbidden_words` wait. ## Review Review run 568 found the README's Repository sentence and the comment on `poll` in `runner/repository.sx` describing a read the code does not make: while the branch has no open PR, the runner reads only once the agent has written its PR text, so a commit message holding a word made before `pull-request.md` exists is found at the turn's end. Its "first word" wording also read as the first word in the message rather than the first of sak's words. b533de6 rewrites both to state the read the code makes, and the same wording in the comment on `commit_hit`. It changes no code. Review run 583 found that sentence still naming the oldest new commit, so that a word in a later commit's message read as no hit, while `commit_hit` names the oldest new commit whose message holds a word. The last commit rewrites the README sentence to say so, and the comment on `found` in `runner/repository.sx` the same way. It changes no code either, so the regression results above, taken at 4a70ceb, hold at the head. ## Gates At 4a70ceb: - `fast`: gate run 309, passed; the review's `fast`, gate run 315, passed. - `full`: gate run 314, failed only at the runner Playwright config's global setup (`sak serve never answered on http://127.0.0.1:18988`). Every earlier step passed, the main Playwright config with 740 passed and 1 skipped. Another process held port 18988 on s32 then, and it has since been ended. Run by hand from a `git archive` arm with `PORT` moved to 18978, the runner config passed 10 of 10. At b533de6: - `full`: gate run 320, passed. Every step passed: the sak and runner builds, `npm run build` (402 vitest tests), the libc grep, every `tests/*.sx` file, release with `tests/step9_release.sx`, the deploy contract, the main Playwright config (740 passed, 1 skipped) and the runner Playwright config (10 passed). - the review's `fast`, gate run 321, passed. At the head 1786318: - `full`: gate run 325, passed. Every step passed: the sak and runner builds, `npm run build` (402 vitest tests), the libc grep, every `tests/*.sx` file, release with `tests/step9_release.sx`, the deploy contract, the main Playwright config (740 passed, 1 skipped) and the runner Playwright config (10 passed).
run_instructions gains a ## Forbidden words part, after the gates: it says
sak publishes no commit message and no pull-request.md that holds one of
the words, ignoring case, then lists every word in order. A sak with no
forbidden words leaves the part out.
The runner reads what the run would publish whenever pull-request.md
changes, whether or not the PR is open, and at each turn's end, instead of
sending SIGTERM on a hit. A hit the session was not told of is posted to
the stream's inbox as a message naming the word and pull-request.md or the
commit, and holds the turn open as its next input. A turn that settles
while a hit the session was told of still stands ends the run failed with
forbidden_words at once.

At the run's end a hit in the PR text still pushes the clean commits and
withholds the text; a hit in a commit's message pushes nothing. The
stream's close carries the word and what it is in.
flow_run_on reads the word and its holder, pull-request.md or a commit's
id, from the result the run's stream closed with, and puts them on the
needs_you event as word and in.
A forbidden_words wait reads "the PR text holds the forbidden word <word>"
or "commit <short id> holds the forbidden word <word>".
While the branch has no open PR, the runner reads only once the agent has
written its PR text. A hit is the first of the forbidden words, in sak's
order, that a message or the text holds.
agra merged commit 612249da12 into task/3694-plan-a-flow-run-waits-on-agra-only-for-a 2026-10-24 17:09:18 +03:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sx/sak!134
No description provided.