feat(start-sdk,start-os)!: let a service run an action that takes input - #4062
Merged
Merged
Conversation
StartOS accepts a prefill on the effect (GetActionInputParams carries it, and the container runtime forwards the options object whole), but the hand-written Effects type omitted it. A service reading another package's action form for a particular target, such as Tor's Add Onion Service for one of its own interfaces, could not say which target without a cast. startosTypeValidation did not catch it: it checks that each generated params type is assignable to the Effects parameter, and a binding with an extra field still is. Helix-Harness: claude-code Helix-Model: claude-opus-5-5
…form it opened A service could not run an action that takes input, its own or another's. The SDK's run validates against the form getInput built under the same event id, and each effect call runs under a fresh procedure id, so the run found no form and threw "getActionInput has not been called". effects.action.run now takes the eventId getInput returned and runs under it, the handshake `start-cli package action run --event-id` already uses. sdk.action.run is built on it: `input` becomes a function from the opened form to the input, and the helper opens the form, applies the function and runs under that form's event id. It also gains `packageId`, to run another service's action that `access` admits, and `prefill`. A plain `input` value is no longer accepted; it could not reach a form. The binding and the start-container man page are regenerated. The actions page gains a section on running another service's action, and the unreleased 3.0.0 changelog entry replaces the prefill-only one. Helix-Harness: claude-code Helix-Model: claude-opus-5-5
MattDHill
approved these changes
Sep 23, 2026
dr-bonez
approved these changes
Sep 23, 2026
helix-a
added a commit
that referenced
this pull request
Sep 23, 2026
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
dr-bonez
pushed a commit
that referenced
this pull request
Sep 24, 2026
…r's eventId (#4069) * fix(start-os,start-sdk): run effect-initiated actions under the caller'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 * fix(start-os): keep --event-id on the in-container action CLI 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 * fix(start-os): take --event-id on action run only 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 * docs(start-core): state the EventId comment plainly Helix-Harness: claude-code Helix-Model: claude-opus-5-5
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
A service can run an action that takes input — its own, or another service's that
accessadmits. Before this, both failed:Action.runvalidates against the formgetInputbuilt under the same event id (prevInputSpec[effects.eventId]).effects.action.getInputandeffects.action.runare separate effect calls, and each runs under a freshprocedure_id. That id is#[serde(default)], skipped in the TypeScript type, and a new randomGuidper call.runthrewgetActionInput has not been called for EventID ….The fix uses the handshake the user's CLI already has (
start-cli package action run --event-id):RunActionParamstakes an optionalevent_id. When present, the run executes under it instead of the per-call id. TheEffectsRunActionParamsbinding and thestart-container action runman page are regenerated.Effects.action.rungainseventId?.Effects.action.getInputgainsprefill?, which the generatedGetActionInputParamsand StartOS already accepted but the hand-written type omitted.sdk.action.run(breaking, in the unreleased 3.0.0):inputis now a function from the opened form (spec, plus thevaluefrom the action's prefill function) to the input.getInput, applies the function, and runs under that form'seventId.packageId, which was commented out, so it could only target the service's own actions, andprefill.inputvalue is no longer accepted: it could not answer a form.### Changedentry.Why
Start9Labs/tor-startos#39 makes Tor's Add/Delete Onion Service
access: 'public'so a service can manage its own addresses. Start9Labs/bitcoin-core-startos#309 is the first consumer: after retiring the port its StartOS 0.3.5 version bound, Bitcoin re-attaches that version's peer.onionto its current binding. Nothing in the tree ran an action with input from a service before, which is how the event-id gap went unnoticed. #4045 notes the path was not exercised on a box.For the reviewer to decide
startosTypeValidation.test.tschecks one direction only: each generated params type must be assignable to theEffectsparameter. A binding field missing fromEffectsstill passes, which is howgetInput'sprefillwent unnoticed. I have not made the check two-way here, since it may surface other effects that already differ.Verification
cargo check -p start-coreandmake start-core-format-checkpass.make start-core-ts-bindingsandmake manpageschange onlyEffectsRunActionParams.tsandstart-container-action-run.1.tscpasses, andmake testpasses 17 suites. The newrunAction.test.tsdrives a realActionthrough effects that give each call a fresh event id, as StartOS does. It checks three things: the helper answers the form it opened; a run naming no opened form is still refused; and an action without input skips the form.make bundle,make checkandmake test(131 tests) pass. container-runtime'snpm run checkpasses against the rebuilt bundle.