feat(workflow): add pull request node - #650
Draft
brynary wants to merge 1 commit into
Draft
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a first-class type="pull_request" workflow node and expands PR-creation plumbing so runs can create/adopt a draft GitHub PR mid-run (with committed-snapshot safety) while keeping PR creation idempotent and consistent across workflow engine, server API, CLI, and generated clients.
Changes:
- Introduces a new
pull_requeststage handler type end-to-end (Rust types, workflow registry, OpenAPI + generated TS client). - Adds “latest committed snapshot” support for
--force/force=truePR creation while runs are active (running/blocked/paused), including remote ancestry validation. - Makes PR creation idempotent by detecting/linking an existing matching open PR and handling GitHub 422 create races.
Reviewed changes
Copilot reviewed 31 out of 32 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/packages/fabro-api-client/src/models/stage-handler.ts | Adds PULL_REQUEST stage handler wire value to TS client model. |
| lib/packages/fabro-api-client/src/models/create-run-pull-request-request.ts | Updates TS client docs for force behavior (active snapshot + non-successful conclusions). |
| lib/packages/fabro-api-client/src/api/runs-api.ts | Updates TS client endpoint docs to match new server semantics. |
| lib/foundation/fabro-types/tests/stage_handler.rs | Extends stage-handler serialization/mapping tests for pull_request. |
| lib/foundation/fabro-types/src/stage_handler.rs | Adds StageHandler::PullRequest and handler-type mapping. |
| lib/foundation/fabro-types/src/graph.rs | Adds pull_request to known handler types. |
| lib/foundation/fabro-api/tests/stage_handler_round_trip.rs | Ensures OpenAPI round-trip supports pull_request handler value. |
| lib/components/fabro-workflow/src/services.rs | Threads run-scoped PR runtime inputs through RunServices. |
| lib/components/fabro-workflow/src/pull_request.rs | Re-exports new snapshot/idempotency PR pipeline primitives. |
| lib/components/fabro-workflow/src/pipeline/pull_request.rs | Implements committed snapshot capture + remote verification and idempotent PR open/link behavior. |
| lib/components/fabro-workflow/src/pipeline/mod.rs | Re-exports new PR pipeline APIs. |
| lib/components/fabro-workflow/src/pipeline/initialize.rs | Populates PullRequestRuntime from run options + sandbox env and injects into services. |
| lib/components/fabro-workflow/src/handler/pull_request.rs | New built-in workflow node handler that creates/adopts a draft PR and exports pull_request.* context keys. |
| lib/components/fabro-workflow/src/handler/mod.rs | Registers the new pull_request handler in the default registry. |
| lib/components/fabro-workflow/src/context.rs | Adds pull_request.* workflow context keys. |
| lib/components/fabro-github/src/lib.rs | Adds find_open_pull_request and structured create error for 422 race reconciliation. |
| lib/components/fabro-github/Cargo.toml | Adds url dependency for query encoding in PR lookup. |
| lib/apps/fabro-server/src/server/tests.rs | Expands server test coverage for PR adoption/idempotency and active snapshot force behavior. |
| lib/apps/fabro-server/src/server/handler/pull_requests.rs | Extends PR create endpoint to support active committed snapshots and linking existing PRs. |
| lib/apps/fabro-server/src/run_files.rs | Exposes reconnect_run_sandbox for server-side active snapshot creation. |
| lib/apps/fabro-cli/tests/it/cmd/pr.rs | Updates CLI help text to reflect “from a run” semantics. |
| lib/apps/fabro-cli/tests/it/cmd/pr_create.rs | Updates CLI help text for --force meaning. |
| lib/apps/fabro-cli/src/commands/run/runner.rs | Soft-loads GitHub creds when graph contains an explicit pull_request node. |
| lib/apps/fabro-cli/src/commands/pr/create.rs | Updates logging message to “Created or linked” PR semantics. |
| lib/apps/fabro-cli/src/args.rs | Updates CLI arg docs and subcommand docs for PR create behavior. |
| docs/public/workflows/stages-and-nodes.mdx | Documents the new type="pull_request" node and its semantics/requirements. |
| docs/public/reference/dot-language.mdx | Adds pull_request as an explicit node type and documents attributes/constraints. |
| docs/public/reference/cli.mdx | Updates CLI docs for fabro pr create and --force. |
| docs/public/execution/run-configuration.mdx | Notes pull_request node requires run branch enabled + pushed. |
| docs/public/execution/context.mdx | Documents pull_request.* context keys emitted by the node. |
| docs/public/api-reference/fabro-api.yaml | Updates OpenAPI description and handler enum; clarifies force behavior and responses. |
| Cargo.lock | Locks the new url dependency addition. |
Comments suppressed due to low confidence (1)
lib/components/fabro-workflow/src/handler/pull_request.rs:126
- When a run already has a stored pull request link, the handler falls back to base_branch="main" and head_branch="" if runtime capability is missing. That can publish incorrect
pull_request.base_branch/pull_request.head_branchcontext (and an empty head branch) even though the run projection already carries git branch + run branch metadata.
if let Some(link) = run_state.pull_request.as_ref() {
let runtime = services.run.pull_request.as_ref();
let base_branch = runtime
.and_then(|runtime| runtime.base_branch.as_deref())
.unwrap_or("main");
let head_branch = runtime
.and_then(|runtime| runtime.head_branch.as_deref())
.unwrap_or("");
return Ok(outcome_for_pull_request(link, base_branch, head_branch));
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+616
to
+618
| /// Capture the sandbox's committed `HEAD`, compute the cumulative diff from | ||
| /// `base_sha` to that exact commit, and ensure that commit is present on the | ||
| /// remote run branch. Dirty and untracked work is intentionally excluded. |
Comment on lines
+195
to
+200
| if !force { | ||
| return Err(ApiError::with_code( | ||
| StatusCode::BAD_REQUEST, | ||
| "Run is not finished yet. Pass --force to use its latest committed snapshot.", | ||
| "run_not_finished", | ||
| )); |
Comment on lines
+64
to
+73
| fn validate_github_origin(origin_url: &str) -> Result<(), Error> { | ||
| let https_url = fabro_github::ssh_url_to_https(origin_url); | ||
| fabro_github::parse_github_owner_repo(&https_url) | ||
| .map(|_| ()) | ||
| .map_err(|_| { | ||
| Error::Precondition( | ||
| "pull_request nodes require a valid github.com repo origin".to_string(), | ||
| ) | ||
| }) | ||
| } |
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.
Summary
type="pull_request"workflow node that creates or links a draft PR during a run and exposes its metadata throughpull_request.*contextfabro pr create --forceto use eligible active or failed-run snapshots while preserving the existing terminal-only default behaviorAn in-run PR intentionally remains draft. Post-run handling returns its URL without refreshing the body, marking it ready, or enabling auto-merge.
Verification
cargo +nightly-2026-04-14 fmt --check --allcargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warningscargo build --workspacecargo nextest run -p fabro-workflowcargo nextest run -p fabro-servercargo nextest run -p fabro-clicargo nextest run -p fabro-githubcargo nextest run -p fabro-validatebun run generateandbun run typecheckinlib/packages/fabro-api-clientgit diff --checkinstasnapshots