You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Do not end workflow files with bare top-level `await workflow(...).run(...)`.
282
282
283
+
### 1b. Make commit and PR boundaries explicit
284
+
285
+
Workflows do **not** get a PR for free just because they pass validation. If the intended deliverable is a branch, commit, push, or GitHub PR, the workflow itself must own that boundary explicitly and document the expected file scope.
286
+
287
+
Use this pattern only when the workflow is supposed to own repository delivery:
288
+
289
+
1. Preflight the git state and fail on unexpected staged changes.
290
+
2. Create or verify the intended branch.
291
+
3. Run implementation, review, soft validation, fix, and hard validation gates.
292
+
4. Stage only the declared target files and review/signoff artifacts.
293
+
5. Commit with a deterministic message.
294
+
6. Push the branch.
295
+
7. Use `createGitHubStep({ action: 'createPR', ... })` from `@agent-relay/sdk/github` to open the PR.
296
+
8. Verify the PR URL/state deterministically and write it into the final signoff artifact.
297
+
298
+
Do not hide commit/PR work in agent prose. Model it as deterministic steps whenever possible. For PR creation, issue updates, file reads, or any GitHub operation, prefer `createGitHubStep` over shelling out to `gh`; it is bundled with `@agent-relay/sdk`. The downstream hard gate must still verify the PR exists before signoff.
299
+
300
+
If commit or PR creation is intentionally outside the workflow, say that directly in the workflow description and signoff so the operator knows to do it after completion.
Raw triple-backtick code fences inside large inline `task: \`...\`` template strings are fragile and can break outer TypeScript parsing, especially when they contain language tags like `swift` or `diff`.
@@ -331,7 +350,7 @@ The battle-tested template:
331
350
-**Use `grep -vE "^(...)$"` for full-line match.** Substring matches bleed across unrelated files (e.g., `setup.ts` would also match `packages/core/src/bootstrap/setup.ts`).
332
351
-**Append `|| true` to the grep.** Without it, an empty result triggers `set -e` and the whole preflight fails before the `if` can even run.
333
352
-**Check the staging area separately.** A dirty index is different from a dirty working tree and both must be clean (modulo allow-list).
334
-
-**Check `gh auth status` early** if any downstream step uses `gh pr create` or similar. Failing on auth at the end of a long DAG is painful.
353
+
-**Check `gh auth status` early** if downstream GitHub operations will use the local transport. Failing on auth at the end of a long DAG is painful.
335
354
336
355
**Never use `git diff --quiet` alone as your "clean tree" check.** It fails on any dirty file, including the ones the workflow is expected to rewrite, which causes false failures on every resume / re-run.
337
356
@@ -374,7 +393,7 @@ command: [
374
393
375
394
Results in `set -e && cat > /tmp/f <<EOF && line 1 && line 2 && EOF && next-command` — a shell syntax error because `&&` cannot appear inside a heredoc body. Use `.join('\n')` or (better) sidestep the heredoc entirely.
376
395
377
-
**The `printf` + `mktemp` alternative — use this for commit messages, PR bodies, and any other multi-line file content.** It avoids heredocs altogether and composes with `.join(' && ')`:
396
+
**The `printf` + `mktemp` alternative — use this for commit messages, raw-CLI fallback PR bodies, and any other multi-line file content.** It avoids heredocs altogether and composes with `.join(' && ')`:
378
397
379
398
```ts
380
399
command: [
@@ -388,7 +407,7 @@ command: [
388
407
].join(' && '),
389
408
```
390
409
391
-
This pattern is specifically recommended over `git commit -m "$(cat <<'EOF' ... EOF)"` and `gh pr create --body "$(cat <<'BODY' ... BODY)"`. Nesting a heredoc inside `$(...)` forces the shell to match a closing paren across many lines of unparsed body text, and any stray parenthesis in the body text can silently break the match. `--body-file` + `mktemp` + `printf` is immune to that entire class of bug.
410
+
This pattern is specifically recommended over `git commit -m "$(cat <<'EOF' ... EOF)"` and raw `gh pr create --body "$(cat <<'BODY' ... BODY)"`. Nesting a heredoc inside `$(...)` forces the shell to match a closing paren across many lines of unparsed body text, and any stray parenthesis in the body text can silently break the match. `--body-file` + `mktemp` + `printf` is immune to that entire class of bug. For workflow-owned PR creation, prefer `createGitHubStep` over raw `gh`; this shell pattern is for raw CLI fallback cases.
392
411
393
412
### 2d. Template-literal escape sequences are processed once before the string is rendered
394
413
@@ -579,8 +598,8 @@ For bug-fix or reliability workflows, do **not** stop at unit or integration tes
579
598
8.**Record residual risks**
580
599
- Call out what was not covered
581
600
9.**Ship the result as a PR**
582
-
- Open the pull request from the workflow itself with the GitHub primitive
583
-
- See [Shipping the Result — Open a PR via the GitHub Primitive](#shipping-the-result--open-a-pr-via-the-github-primitive) below
601
+
- Open the pull request from the workflow itself with `createGitHubStep`
602
+
- See [Shipping the Result — Open a PR via `createGitHubStep`](#shipping-the-result--open-a-pr-via-creategithubstep) below
584
603
- A workflow that fixes a bug and stops short of the PR has only done half the loop
585
604
586
605
### Clean-environment validation guidance
@@ -602,38 +621,33 @@ If the right proving environment is unclear, first write a **meta-workflow** tha
602
621
603
622
This is often better than jumping straight to implementation.
604
623
605
-
## Shipping the Result — Open a PR via the GitHub Primitive
624
+
## Shipping the Result — Open a PR via `createGitHubStep`
606
625
607
-
A workflow whose final artifact is "a clean working tree on a sandbox you'll throw away" has not shipped anything. **End every code-changing workflow by opening a pull request, and do it from inside the workflow** using the `@agent-relay/github-primitive`. Don't tell the operator to follow up with `gh pr create` — make the workflow's own last step the PR.
626
+
A workflow whose final artifact is "a clean working tree on a sandbox you'll throw away" has not shipped anything. **End every code-changing workflow by opening a pull request, and do it from inside the workflow** using `createGitHubStep` from `@agent-relay/sdk/github`. Don't tell the operator to follow up with `gh pr create` — make the workflow's own last step the PR.
608
627
609
-
### Why the primitive (and not raw `gh` / `octokit`)
628
+
### Why `createGitHubStep` (and not raw `gh` / `octokit`)
610
629
611
630
The primitive picks the right transport at runtime:
612
631
613
-
| Where the workflow runs | Transport the primitive uses | What you provide |
632
+
| Where the workflow runs | Transport `createGitHubStep` uses | What you provide |
614
633
|---|---|---|
615
634
| Local (`agent-relay run`) |`gh` CLI |`gh auth status` works |
You write **one** workflow. The same `createPR` step opens a PR via your local `gh` when you iterate on it on a laptop, and via the workspace's GitHub App when the same file runs in `agent-relay cloud run`. No branching by environment, no env-var sniffing in your task strings, no "this part only works in cloud" caveats. That's the whole point of the adapter.
620
639
621
-
> **Phase C interaction (cloud only):**`agent-relay cloud run` already auto-pushes per-`paths[]` diffs as separate PRs after the workflow callback when the repos are allowlisted (see `pushedTo` in the run record). Phase C is the *catch-all* — if your workflow does nothing else, you still get one PR per declared path. Use the github primitive**on top of** that when you need PRs the catch-all can't produce: cross-cutting issues, follow-up tracking issues, opening one PR that spans multiple paths, draft PRs you want labeled/assigned in specific ways, or PRs against a repo you didn't `paths[]` in.
640
+
> **Phase C interaction (cloud only):**`agent-relay cloud run` already auto-pushes per-`paths[]` diffs as separate PRs after the workflow callback when the repos are allowlisted (see `pushedTo` in the run record). Phase C is the *catch-all* — if your workflow does nothing else, you still get one PR per declared path. Use `createGitHubStep`**on top of** that when you need PRs the catch-all can't produce: cross-cutting issues, follow-up tracking issues, opening one PR that spans multiple paths, draft PRs you want labeled/assigned in specific ways, or PRs against a repo you didn't `paths[]` in.
The primitive's actions are stable across runtimes: `getRepo`, `createBranch`, `createFile`, `updateFile`, `createPR`, `updatePR`, `getPR`, `listPRs`, `mergePR`, `createIssue`, etc. See `packages/github-primitive/src/types.ts` for the full enum.
697
+
`createGitHubStep` is bundled with `@agent-relay/sdk`; do not add a separate install. Its actions are stable across runtimes: `getRepo`, `createBranch`, `createFile`, `updateFile`, `createPR`, `updatePR`, `getPR`, `listPRs`, `mergePR`, `createIssue`, etc. See the SDK GitHub primitive docs for the full enum.
687
698
688
699
### Authoring rules for PR-shipping workflows
689
700
@@ -692,7 +703,7 @@ The primitive's actions are stable across runtimes: `getRepo`, `createBranch`, `
692
703
3.**Branch name encodes the run.**`agent-relay/run-${runId}` or `agent-relay/${workflow-name}-${timestamp}` so reviewers can tell the PR apart from other automation, and so reruns don't clash.
693
704
4.**`draft: true` while iterating.** Once the workflow is stable end-to-end, flip to `draft: false`.
694
705
5.**Body is a real PR description.** Summary + Test plan, generated from the workflow's own evidence (verification step output, diff stats, test run output). If you find yourself writing a placeholder body, the workflow isn't done — capture the real evidence in an earlier step and template it in.
695
-
6.**Don't use the primitive to substitute for `paths[]` push-back in cloud.** If the diff lives in a tarballed `paths[]` mount, let cloud's Phase C push-back open that PR (it handles the patch generation, branch lifecycle, and per-repo allowlist). Use the primitive when you need a PR against a repo or branch outside the `paths[]` set, or when you want to add an extra PR (e.g. a tracking issue, a follow-up against a sibling repo, a docs-only PR).
706
+
6.**Don't use `createGitHubStep`to substitute for `paths[]` push-back in cloud.** If the diff lives in a tarballed `paths[]` mount, let cloud's Phase C push-back open that PR (it handles the patch generation, branch lifecycle, and per-repo allowlist). Use `createGitHubStep` when you need a PR against a repo or branch outside the `paths[]` set, or when you want to add an extra PR (e.g. a tracking issue, a follow-up against a sibling repo, a docs-only PR).
696
707
7.**Failure is a real failure.** If `createPR` errors (auth, permissions, branch conflict), the workflow should fail the step, not warn-and-continue. A "successful" workflow that silently failed to open the PR is the worst-case outcome — the human thinks the work shipped.
697
708
698
709
### Where this fits in the bug-fix phases
@@ -1305,7 +1316,7 @@ When you set `.pattern('supervisor')` (or `hub-spoke`, `fan-out`), the runner au
1305
1316
| Hardcoding all channels at spawn time | Use `agent.subscribe()` / `agent.unsubscribe()` for dynamic channel membership post-spawn |
1306
1317
| Using `preset: 'worker'` for Codex in *interactive team* patterns when coordination is needed | Codex interactive mode works fine with PTY channel injection. Drop the preset for interactive team patterns (keep it for one-shot DAG workers where clean stdout matters) |
1307
1318
| Separate reviewer agent from lead in interactive team | Merge lead + reviewer into one interactive Claude agent — reviews between rounds, fewer agents |
1308
-
| Not printing PR URL after `gh pr create` | Add a final deterministic step: `echo "PR: $(cat pr-url.txt)"` or capture in the `gh pr create` command |
1319
+
| Not printing PR URL after `createGitHubStep({ action: 'createPR' })` | Capture `html_url` with `output: { mode: 'data', format: 'json', path: 'html_url' }` and echo or write it in a final deterministic step |
1309
1320
| Workflow ending without worktree + PR for cross-repo changes | Add `setup-worktree` at start and `push-and-pr` + `cleanup-worktree` at end |
0 commit comments