Skip to content

examples: software-factory and stale-issues for the README use cases - #450

Merged
khaliqgant merged 3 commits into
mainfrom
examples/readme-use-cases
Sep 17, 2026
Merged

khaliqgant merged 3 commits into
mainfrom
examples/readme-use-cases

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Sep 17, 2026 •

Copy link
Copy Markdown
Member

Two example flows the README's "What can you do with this?" section points at, authored against the real surface package and registered in examples/tsconfig.json (npm --prefix packages/surface run typecheck:examples passes; flows check passes on both, the Slack one under RELAYFLOWS_SLACK_MOCK=1).

  • examples/software-factory/ — deployable today: flows deploy … --repo o/r --on linear:team=ENG --approver you. Implementer → deterministic tests → adversarial review (verdict is a file) → tests → gh pr create; a blocked review opens a draft PR with the findings and ends step_failed.
  • examples/stale-issues/ — the scheduled digest: deterministic GitHub fetch, one f.llm with a JSON-schema gate, one f.slack.post. Targets flows schedule (in flight); documents flows run --cloud on demand until then.

The Review use case points at the existing examples/pr-review-pipeline/, which #434's successor is making runnable.

🤖 Generated with Claude Code


Note

Low Risk
Additive example and documentation only; no changes to core runtime, though the samples exercise shell, GitHub, and Slack when users deploy them.

Overview
Adds two README-linked example flows under examples/, each with a short README and a @relayflows/surface flow file, and registers them in examples/tsconfig.json for example typechecking.

software-factory wires ticket-driven automation: implementer agent → gated summary.md → deterministic npm test (exit-code gated) → adversarial reviewer that must write review.passed or review.blocked under .relayflow → tests again → commit/push and gh pr create (draft with findings when blocked, step_failed). Deploy/local run instructions cover flows deploy with --on triggers and --local-agent.

stale-issues is a scheduled digest pattern: validated repo name, journaled Node fetch of all open issues (paginated, no PRs) → single f.llm triage behind JSON schema → Slack post with mrkdwn escaping and issue numbers filtered to the fetched set. Documents flows schedule and interim flows run --cloud plus RELAYFLOWS_SLACK_MOCK=1 for local check.

Both examples emphasize untrusted input: shell arguments via shellWord/quoting, artifacts outside git’s tracked tree, and deterministic gates instead of agent-reported pass/fail.

Reviewed by Cursor Bugbot for commit 7306df1. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Adds two runnable example flows for the README's "What can you do with this?" section, both registered in examples/tsconfig.json and passing flows check, with a hardening pass so they're safe to deploy with untrusted input.

  • examples/software-factory maps a ticket to a PR: implementer agent → deterministic tests → adversarial review (verdict written to a file) → tests → gh pr create; a blocked review opens a draft PR with findings and ends step_failed.
  • examples/stale-issues fetches open issues deterministically, classifies them with one LLM step behind a JSON-schema gate, and posts a single Slack digest; flows schedule is in flight, so the README documents flows run --cloud until it ships.

Security hardening

  • Ticket text is single-quoted into shell arguments so crafted titles can't run commands, and flow artifacts live outside the tracked tree.
  • The GitHub fetch validates the repo, paginates, and only fetched issues appear in the digest; model output is schema-validated and escaped for Slack mrkdwn.

Written for commit 7306df1. Summary will update on new commits.

Review in cubic

…digest)

Two README use-case flows authored against the real @relayflows/surface
package and registered in examples/tsconfig.json.

software-factory deploys today: `flows deploy … --on linear:team=ENG`
launches implementer → deterministic tests → adversarial review agent whose
verdict is a file → tests again → `gh pr create` (draft + findings when the
review blocks, ending step_failed).

stale-issues is the scheduled automation: deterministic issue fetch, one
f.llm step with a JSON schema gate, one Slack digest. `flows schedule` is in
flight; until it ships the README shows `flows run --cloud` on demand.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 16 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: c9afe626-d639-4798-9ab7-d29aa92541e7

📥 Commits

Reviewing files that changed from the base of the PR and between fe8d760 and 7306df1.

📒 Files selected for processing (5)
  • examples/software-factory/README.md
  • examples/software-factory/software-factory.flow.ts
  • examples/stale-issues/README.md
  • examples/stale-issues/stale-issues.flow.ts
  • examples/tsconfig.json

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 6 potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review


await f.run(TEST, { timeout: "15m" });

const verdict = await f.run("if [ -f review.passed ]; then echo PASSED; else echo BLOCKED; fi");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Existing pass marker overrides review

When the checkout contains review.passed, verdict reports PASSED even if the adversary writes review.blocked. The flow opens a non-draft PR despite a blocked review.

Learn more

The verdict files live in the repository workspace, so either one can predate the adversary step. The current test checks only whether review.passed exists. A blocked verdict cannot override an existing pass marker, and simultaneous markers also resolve as passed.

Example: The target repository already tracks review.passed. The adversary writes review.blocked after finding a regression. The command still prints PASSED, and the flow opens a ready-for-review PR instead of the promised blocked draft.

Recommended fix: Remove both verdict markers immediately before the adversary runs, then accept PASSED only when review.passed exists and review.blocked does not. Treat both present or both absent as blocked or as a gate failure.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a3fdef0: artifacts live in .relayflow/ which is wiped at the start of every run, and PASSED requires review.passed present AND review.blocked absent.

Comment on lines +20 to +21
`curl -sf -H "Authorization: Bearer $GH_TOKEN" "https://api.github.com/repos/${input.repo}/issues?state=open&per_page=100" ` +
`| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const now=Date.now();console.log(JSON.stringify(JSON.parse(s).filter(i=>!i.pull_request).map(i=>({number:i.number,title:i.title,labels:i.labels.map(l=>l.name),updatedDaysAgo:Math.floor((now-Date.parse(i.updated_at))/864e5),comments:i.comments}))))})'`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue discovery stops after 100

Repositories with over 100 open issues make issues fetch only the first page. The digest undercounts issues and omits later pages from triage.

Learn more

GitHub's REST issues endpoint paginates results, and per_page=100 only raises one page's limit. The command never follows response pagination links. Filtering pull requests after fetching does not expose omitted pages.

Example: A repository has 140 open issues and no open pull requests. The request returns 100, the digest reports 100 open issues, and issues 101–140 never reach the classifier.

Recommended fix: Fetch every page before filtering and mapping. Prefer gh api --paginate --slurp or implement Link-header pagination, then combine all pages into one JSON array.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a3fdef0: the fetch now runs under node, follows pagination (sort=updated&direction=asc, up to 20 pages) and only stops on a short page.

Comment on lines +32 to +33
{ output: { type: "object", required: ["stale", "attention"], properties: {
stale: { type: "array" }, attention: { type: "array" } } } },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Malformed triage entries pass validation

When f.llm returns malformed entries, triage accepts them because both arrays omit items schemas. Null entries crash digest construction; incomplete objects post undefined values.

Learn more

The cast to Triage does not add runtime checks. JSON Schema arrays without an items declaration accept values of any type. The later formatter assumes every item is a non-null object with numeric number and string title and reason fields.

Example: A valid response under the current schema is { "stale": [null], "attention": [] }. Validation passes, but triage.stale.map(line) throws while reading i.number, so no digest is posted.

Recommended fix: Give both arrays an items object schema with required: ["number", "title", "reason"], matching property types, and additionalProperties: false. Keep the top-level required fields and consider disallowing extra top-level properties as well.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a3fdef0: both arrays declare items (integer number ≥ 1, bounded title/reason, additionalProperties: false, maxItems: 50); findings are also filtered to issue numbers that were actually fetched.

Comment on lines +20 to +21
`curl -sf -H "Authorization: Bearer $GH_TOKEN" "https://api.github.com/repos/${input.repo}/issues?state=open&per_page=100" ` +
`| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const now=Date.now();console.log(JSON.stringify(JSON.parse(s).filter(i=>!i.pull_request).map(i=>({number:i.number,title:i.title,labels:i.labels.map(l=>l.name),updatedDaysAgo:Math.floor((now-Date.parse(i.updated_at))/864e5),comments:i.comments}))))})'`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟥 Repository input enables command injection

A crafted input.repo injects shell syntax into f.run. The command executes with the flow worker's repository and GitHub credentials.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a3fdef0: input.repo is validated against owner/name before any step, and the fetch takes it as a node argv (node -e '…' 'owner/name') rather than interpolating it into the shell.

Comment on lines +59 to +63
await f.run(`gh pr create --title ${JSON.stringify(issue.title)} --body-file summary.md`);
return f.done("success");
}
await f.run("{ cat summary.md; printf '\\n\\n## Adversarial review: BLOCKED\\n\\n'; cat review.md; } > pr-body.md");
await f.run(`gh pr create --draft --title ${JSON.stringify(`[blocked] ${issue.title}`)} --body-file pr-body.md`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟥 Issue titles enable command injection

A ticket title containing shell substitution survives JSON.stringify inside f.run. Creating either PR executes attacker-controlled commands with the worker's credentials.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a3fdef0: titles go through shellWord (single-quoted, the SURFACE.md idiom) and are capped at 200 chars; nothing from the ticket reaches /bin/sh unquoted any more.

Comment on lines +32 to +33
{ output: { type: "object", required: ["stale", "attention"], properties: {
stale: { type: "array" }, attention: { type: "array" } } } },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 Unvalidated model output forges Slack content

Arbitrary LLM fields flow into Slack markup because the schema omits item validation. A model response can forge links, mentions, and issue identities under the integration's identity.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a3fdef0: model text is escaped for Slack mrkdwn (& < >), links are built only from the validated repo and the integer issue number, and findings not in the fetched set are dropped.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread examples/software-factory/software-factory.flow.ts Outdated
Comment thread examples/software-factory/software-factory.flow.ts Outdated
Comment thread examples/stale-issues/stale-issues.flow.ts Outdated
@github-actions

github-actions Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Review swarm: maintainability

No fresh transcript was produced for run 3fcc3ce5-decd-4e26-ba31-fcd3e5de9672 (MISSING).

@github-actions

github-actions Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Review swarm: history

No fresh transcript was produced for run 3fcc3ce5-decd-4e26-ba31-fcd3e5de9672 (MISSING).

@github-actions

github-actions Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Review swarm: structure

No fresh transcript was produced for run 3fcc3ce5-decd-4e26-ba31-fcd3e5de9672 (MISSING).

@github-actions

github-actions Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Review swarm: FAILED

  • maintainability: MISSING
  • history: MISSING
  • structure: MISSING

Cloud run: 3fcc3ce5-decd-4e26-ba31-fcd3e5de9672

…tput, fix verdict and git add

Review round on #450 (Devin, Cursor), all valid:

- software-factory: ticket titles reached `gh pr create` through
  JSON.stringify, which leaves `$(…)` and backticks live under /bin/sh —
  a crafted Linear/GitHub title ran commands with the worker's token.
  Titles now pass through `shellWord` (single-quoted, the SURFACE.md
  idiom) and are capped. Flow artifacts moved to `.relayflow/`, excluded
  via `.git/info/exclude`, so `git add -A` needs no pathspec (the `:!`-only
  form errored) and a leftover `review.passed` from a previous run cannot
  override a fresh `review.blocked`; PASSED now requires the pass marker
  and the absence of the block marker.
- stale-issues: `input.repo` is validated as owner/name before use and the
  fetch runs under node with the repo as an argv, never interpolated into
  the shell; GitHub pagination is followed (oldest-updated first) so
  repositories over 100 issues are triaged in full; the LLM schema now
  declares `items` (integer number, bounded strings, no extra keys);
  digest lines only reference issue numbers that were actually fetched and
  escape model text for Slack mrkdwn, so a response cannot forge links or
  mentions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a3fdef0. Configure here.

Comment thread examples/stale-issues/stale-issues.flow.ts Outdated
…node -e

Cursor on #450: node -e may place an [eval] placeholder at argv[1], so
argv.slice(1)[0] can be the wrong slot. Use argv.at(-1), which is the
quoted repository on every Node version; probed the request URL.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@khaliqgant
khaliqgant merged commit 03eeee6 into main Sep 17, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant