fix(start-os,start-sdk): run effect-initiated actions under the caller's eventId - #4069
Merged
Merged
Conversation
…r's eventId The container runtime tags every effect call with the calling procedure's event id under `eventId`, but the action effects declared the field as `procedure_id`, which serde reads from `procedureId`. So every effect-initiated get-input, run and task lookup reached a service's actor under a fresh random id. ConcurrentActor skips a message's conflicts with a running handler of the same id, so those calls lost that re-entrancy, and a getInput and the run answering it landed under different ids. That is why #4062 added an explicit `eventId` to effects.action.run, and why it still failed on a box: the runtime overwrote it with the caller's procedure id. The three effect param structs now flatten one `EventId` (prelude.rs), the single owner of the envelope field, and the service code names that id `event_id` all the way to the runtime's `execute`. A form and the run answering it now share the caller's procedure id with nothing passed, so #4062's explicit `eventId` on effects.action.run and in sdk.action.run is removed. The unused `EventId` binding goes with the old struct. A unit test deserializes each params struct from an envelope and checks the id arrives. Helix-Harness: claude-code Helix-Model: claude-opus-5-5
The previous commit flattened `EventId` into the effect params with `#[arg(skip)]`, which removed `start-container action run --event-id` (added in #4062). Without it every in-container CLI call gets a fresh id, so `run` can never answer the form `get-input` opened. `EventId` is now a clap arg group: `--event-id` on both `get-input` and `run`, flattened after `--package-id`. Its field is an `Option<Guid>` so neither clap nor the man pages carry a random default, and `EventId::or_new` resolves it for the handlers. Its comment is a plain `//`: as a doc comment, clap took it as the about text of every command that flattens it, replacing the `get-input` and `run` descriptions. Against master the man pages differ only by `--event-id` on `get-input`. A new test parses `--event-id` for both commands and checks it survives serialization to the request. Helix-Harness: claude-code Helix-Model: claude-opus-5-5
A CLI command takes an event id when it continues an event an earlier call started: `run` answers the form `get-input` opened. `get-input` starts the event and returns its id, so the flag has nothing to name there. This matches `start-cli package action`, where only `run` has `--event-id`. get-input keeps reading the envelope id; the man pages match master again. Helix-Harness: claude-code Helix-Model: claude-opus-5-5
Member
Author
|
End-to-end test on a VM, using this PR's CI build: compile run 35914236933, Setup
Before this build (the boot just before the upgrade, same packages), the run was refused: With this build, the first boot's init did the reattach. Bitcoin's init ran as procedure Resulting state
Traffic: a P2P |
dr-bonez
reviewed
Sep 23, 2026
Helix-Harness: claude-code Helix-Model: claude-opus-5-5
dr-bonez
approved these changes
Sep 23, 2026
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 changes
One name for the calling procedure's id:
eventId, from the runtime's envelope to the service actor.service/effects/prelude.rs: the unusedEventIdstruct is now the single owner of the caller's event id. It is a serde field (eventId, as the runtime sends it) and a clap arg (--event-id). The three action effect params (GetActionInputParams,RunActionParams,CreateTaskParams) flatten it in place of theirprocedure_idfields, andEventId::or_newresolves it for the handlers. Two details:Option<Guid>, so neither clap nor the man pages carry a random default.//: as a doc comment, clap takes it as the about text of every command that flattens it.service/action.rs,service/mod.rs,service/persistent_container.rs:procedure_id/idare renamed toevent_idwherever they carry this id, down to the runtime'sexecute.eventIdon the effect is folded into this one. For a service,Effects.action.runandsdk.action.runpass nothing; the runtime supplies the id. The in-container CLI keepsstart-container action run --event-id, soget-input→run --event-idanswers a form from a shell. A command takes the flag when it continues an event an earlier call started;get-inputstarts one and returns its id, so it has none, as instart-cli package action. The man pages are unchanged from master. TheEffectsRunActionParamsbinding loseseventId, and the unusedEventIdbinding goes; no TypeScript imported either.Why
The container runtime sets
eventIdon every effect call to the calling procedure's id (EffectCreator.rpcRoundFor). The action effects declared the field asprocedure_id, which serde reads fromprocedureId, so they never saw that id. Every effect-initiatedget-input,runand task-input lookup reached the target'sConcurrentActorunder a fresh randomGuid.That actor skips a message's conflicts with a running handler of the same id (
util/actor/concurrent.rs,&id != hid). So the mismatch cost two things:create_taskalready works around a self-call deadlock withtry_get.getInputand therunanswering it never shared an id.#4062 patched the second with an explicit
eventIdoneffects.action.run. On a VM running #4062's build that still failed: the runtime overwrote the field with the caller's own procedure id, and Tor refused Bitcoin's run withgetActionInput has not been called for EventID …. #4068 tried to let that field win over the tag; it is closed in favour of this, per dr-bonez: read the id that is already sent, under the name it is sent under.With this change, a form opened by
effects.action.getInputand theeffects.action.runanswering it share the calling procedure's id, and nothing extra is passed.sdk.action.runkeeps its shape: it opens the form, applies theinputfunction and runs. Its constraint: the target keys a form by the caller's procedure id, so one procedure answers one form at a time. The helper and the book say so.Verification
service::effects::action::test:eventId.runparses--event-idand keeps it through serialization to the request;get-inputrejects the flag.cargo test --features=test -p start-core --lib -- --skip export_: 587 pass.util::http_reader::main_testfails because it fetcheshttps://docs.start9.com/llms.txt, which times out from the host I ran it on.cargo check -p start-coreandmake start-core-format-checkpass.make start-core-ts-bindingsandmake manpageschange only the files listed above.tscpasses and jest passes 17 suites.runAction.test.tsnow models StartOS: every call from one procedure runs under that procedure's id. It checks that a run answers the form its own procedure opened, and that a run from another procedure is refused.make bundle,make checkandmake test(131 tests) pass. container-runtime:npm run checkandnpm testpass.For the reviewer
Re-entrancy now works as the actor intends for effect-initiated calls. One consequence: two calls from one procedure into the same service no longer wait on each other's conflicts, because they share an id. That matches what the actor already does for calls from inside a handler. No StartOS changelog entry: nothing a user sees changes that I can point to.