Skip to content

Software factory change - #547

Draft
agent-relay-code[bot] wants to merge 2 commits into
mainfrom
relayflow/flows-software-garden-abe21cfb
Draft

agent-relay-code[bot] wants to merge 2 commits into
mainfrom
relayflow/flows-software-garden-abe21cfb

Conversation

@agent-relay-code

@agent-relay-code agent-relay-code Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

done(reason, { detail }): let a flow say why it failed

Closes the gap in the ticket: ctx.done(reason) accepted only the reason, so
every authored step_failed landed on one generic sentence. Cloud run
f92bf832-7848-58d8-b5ca-da8e3b849f1c (cloud#3919) ended that way after twenty
successful steps and an opened PR. The reviewer's actual finding — "one P2
remains: cleanup can report success while an ambiguous allocation stays
invisible through all three sweeps … review.clean was not created"
— existed
and reached no reader. Finding it took opening the Codex reviewer's raw
transcript.

Implemented against reviewed-plan.md.


What changed

Surface (packages/surface)

  • done(reason: FlowCompletionReason, options?: DoneOptions): void, with
    DoneOptions { detail?: string }. The one-argument form is untouched.
  • COMPLETION_DETAIL_MAX_CODE_POINTS = 2000 exported so an author can measure
    a detail before passing one.

SDK (packages/sdk)

  • New src/authored-completion.ts (167 lines) — the authored verdict
    vocabulary (LOWERED_COMPLETIONS, isLoweredCompletion,
    completionMarker), moved out of the 704-line executor, plus
    normalizeCompletionDetail, isDurableCompletionDetail and
    singleLineCompletionDetail. The executor shrank to 686 lines; the CLI
    report and flows status now read the vocabulary without importing the
    executor and everything it pulls in.
  • New src/authored-verdict.ts (87 lines) — a small projection from a root
    journal to { reason, detail? }, kept out of the generic foldRunState.
  • authored-flow-executor.tsdone() validates and normalizes the options
    before markCompletion(), and the terminal marker carries the detail.
  • authored-root.tscompletionDetail on the root step's output;
    isCompletedRootOutput validates it; completedRootResult returns the
    stored value without recomputing it.
  • authored-node-runner.ts — the IPC frame's detail is type- and
    bound-checked, then attested by the existing marker comparison.
  • cli/run.tsRunReport.completionDetail, and the diagnostic's existing
    detail key. For step_failed the detail replaces the generic sentence.
  • cli/status.ts — a labelled authored done("...") line and an
    authored_completion JSON key, beside the kernel facts, which are unchanged.

Kernel — no production change. A regression in
relayflowd/src/server/tests.rs pins the three kernel properties the SDK now
relies on.

Docsdocs/SURFACE.md gains a Saying why section (bound, redaction,
absence semantics, marker shape, the flows status line, the one-line message
encoding) and an exit-code table note; docs/CLOUD.md documents the error
rendering and the one-line encoding.


Design decisions worth reviewing

The detail rides inside the terminal marker. completionMarker(reason)
with no detail returns the exact strings it always did; with one it returns
printf '%s' '<JSON>' where the JSON is
{"completionReason":"<reason>","detail":"<detail>"}, shell-quoted the way
f.hook already quotes its record. This makes the detail a durable journal
fact, and the IPC verifier's existing command comparison attests it for free —
a frame cannot claim a detail the journal does not hold, drop one it does, or
alter a character of it.

Redact, then truncate — and this is not cosmetic. A secret environment
value is redacted by exact replaceAll. Cut it in half first and there is
nothing left for that match to find, so the truncation is what journals a live
fragment. Mutation-verified below.

The bound is 2,000 Unicode code points in the final string, suffix
included.
Deliberately not the kernel's worker_failure_detail bound,
which takes 2,000 chars and then appends its suffix. Code points, not
String.length, which counts UTF-16 units. Over-long details truncate with a
visible … (truncated) rather than being refused.

Absence has four spellings and they all mean the same thing. No argument,
undefined, {}, { detail: undefined } — and whitespace-only joins them.
Nonempty prose is preserved, trimmed. No empty-string refusal was added: that
would be a new runtime failure for a type-valid string.

Local status keeps the kernel's account. An authored step_failed run
does complete with a root step that succeeded, and status,
completion_reason and every step line stay exactly that. The authored verdict
is a separate labelled line. Recognition is by the root's own
relayflows.authored-root.v1 metadata, not the step id — an ordinary flow may
name a step authored-root — and only a terminal step_done / success
completion with a complete, in-bounds output attests a verdict.

The diagnostic message is one line. Cloud renders a run's error through
errorLines, which elides the middle of a long multi-line error. A forty-line
detail rendered as forty lines would lose exactly the finding it carries, so
the message escapes breaks as \n / \r / \t / \uXXXX. The unescaped
normalized text stays in completionDetail and the diagnostic's detail.


Verification

Every claim below carries the literal command and its captured output.
Environment: node v25.6.0, cargo bootstrapped by ops/cargo.sh
(cargo 1.98.1). packages/surface built and installed into
packages/sdk/node_modules with the repo's existing local-surface pattern
(npm install ./../surface --prefix packages/sdk --no-save --ignore-scripts)
before any SDK test was interpreted.

Surface

$ cd packages/surface && npm test
 Test Files  10 passed (10)
      Tests  51 passed (51)
   Start at  22:37:13
   Duration  9.25s (transform 696ms, setup 0ms, collect 1.73s, tests 9.08s, environment 1ms, prepare 429ms)

SDK — every affected suite

$ cd packages/sdk && npx vitest run tests/authored-completion-detail.test.ts \
    tests/authored-status-detail.test.ts tests/authored-detail-live.test.ts \
    tests/authored-root.test.ts tests/authored-node-result.test.ts tests/cloud-read.test.ts \
    tests/authored-step-failed.test.ts tests/authored-declined.test.ts \
    tests/authored-declined-report.test.ts tests/authored-declined-live.test.ts \
    tests/authored-step-failed-exit.test.ts tests/cli-status.test.ts tests/run-state.test.ts \
    tests/cloud-run.test.ts tests/spec-parity.test.ts tests/authored-flow.test.ts \
    tests/authored-human.test.ts tests/authored-admission.test.ts tests/cli.test.ts \
    tests/direct-run-failure.test.ts tests/authored-run-failure-evidence.test.ts
 ✓ tests/cloud-read.test.ts (42 tests) 54ms
 ✓ tests/cloud-run.test.ts (58 tests) 700ms
 ✓ tests/cli.test.ts (65 tests) 1426ms
 ✓ tests/cli-status.test.ts (26 tests) 960ms
 ✓ tests/authored-flow.test.ts (25 tests) 778ms
 ✓ tests/authored-root.test.ts (20 tests) 183ms
 ✓ tests/run-state.test.ts (21 tests) 11ms
 ✓ tests/authored-node-result.test.ts (42 tests) 16ms
 ✓ tests/authored-completion-detail.test.ts (53 tests) 174ms
 ✓ tests/authored-human.test.ts (13 tests) 106ms
 ✓ tests/authored-step-failed.test.ts (10 tests) 36ms
 ✓ tests/authored-status-detail.test.ts (20 tests) 75ms
 ✓ tests/authored-run-failure-evidence.test.ts (8 tests) 937ms
 ✓ tests/spec-parity.test.ts (31 tests) 378ms
 ✓ tests/authored-declined.test.ts (13 tests) 60ms
 ✓ tests/authored-step-failed-exit.test.ts (3 tests) 8ms
 ✓ tests/direct-run-failure.test.ts (8 tests) 15ms
 ✓ tests/authored-declined-report.test.ts (6 tests) 7ms
 ✓ tests/authored-admission.test.ts (2 tests) 3ms
 ✓ tests/authored-detail-live.test.ts (2 tests) 3328ms
 ✓ tests/authored-declined-live.test.ts (1 test) 1946ms
 Test Files  21 passed (21)
      Tests  469 passed (469)

Typechecking (both configs, plus the new test files added to
tsconfig.tests.json's include list):

$ cd packages/sdk && npm run typecheck && npm run typecheck:tests
> @relayflows/sdk@2.0.25 typecheck
> tsc --noEmit && tsc -p tsconfig.type-tests.json

> @relayflows/sdk@2.0.25 typecheck:tests
> tsc -p tsconfig.tests.json

(No diagnostics; both exited 0.)

Kernel

$ cd kernel && sh ../ops/cargo.sh test -p relayflowd --lib
test server::tests::deterministic_marker_carrying_json_survives_reopen_and_refuses_changed_detail ... ok
test result: ok. 54 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.66s

The new test asserts, through the protocol verbs: the marker command with JSON
survives admission and a journal reopen character for character; re-admitting
the identical spec under the same admission key returns the same run_id and
one run.spawned; changing one word of the detail under that key is refused
with run_admission_conflict.

Spec-hash stability against pre-change fixtures

The four no-detail lowered specs were captured from the tree before this
change (git stash, capture, git stash pop; base commit 16237b6) and are
hardcoded in tests/authored-completion-detail.test.ts as PRE_CHANGE_SPEC.
The pre-change capture, truncated to fit:

$ cd packages/sdk && npx vitest run tests/zz-capture-spec.test.ts   # on the stashed tree
FIXTURE success 2bca7a2d3291b698623671e3527c0fbfa9fca0ad0a032996749d663b27f7549a {"name":"stability-success/complete-1",...
FIXTURE needs_human d8504f8cc11ff05d8a389f9c177a06d905e9083c8fa006783c866ef2ec892b61 {"name":"stability-needs_human/comp...
FIXTURE step_failed e749f41b3b319b10ab77e2ee57a5be4b8eecd8bbd5fe20d5c73eb980d111ed70 {"name":"stability-step_failed/comp...
FIXTURE declined f62919dc650b5ffcaa79f0b763b732a2fe5ebd9e36e924f26753d0077111242f {"name":"stability-declined/complete-1...

The test re-derives the complete canonical spec and its sha256 for each of the
four reasons, for each of done(r), done(r, {}), done(r, {detail: undefined}) and done(r, {detail: ' '}) — sixteen cases — and compares
against these bytes. The capture file was removed after use; it is not part of
the commit.

Mutation verification

Two claims, each reverted, failed, restored byte-for-byte, and re-passed.

M1 — redaction runs before truncation. Reverted to redact(bound(detail)):

$ npx vitest run tests/authored-completion-detail.test.ts -t "redacts BEFORE"
   × normalizing done()'s optional detail > redacts BEFORE it truncates, so the cut cannot create a leak 16ms
     → expected 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx…' not to contain 'SSSSSSSS'
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
      Tests  1 failed | 52 skipped (53)

Restored (diff against the pre-mutation copy produced no output) and re-run:

$ npx vitest run tests/authored-completion-detail.test.ts -t "redacts BEFORE"
 ✓ tests/authored-completion-detail.test.ts (53 tests | 52 skipped) 3ms
      Tests  1 passed | 52 skipped (53)

M2 — the detail replaces the generic step_failed sentence. Reverted the
message branch to the unconditional generic string, rebuilt, and ran both the
unit and the live end-to-end suite:

$ npx vitest run tests/authored-completion-detail.test.ts tests/authored-detail-live.test.ts
 ❯ tests/authored-completion-detail.test.ts (53 tests | 2 failed) 166ms
   × the report a detail-bearing completion produces > replaces the generic step_failed sentence with what the flow said 7ms
     → expected 'Flow "software-factory" declared done…' to be 'Flow "software-factory" declared done…' // Object.is equality
   × the report a detail-bearing completion produces > folds a multiline detail onto the message line and keeps the original beside it 1ms
     → expected 'Flow "software-factory" declared done…' to contain 'P1: none\nP2: review.clean was not cr…'
 ❯ tests/authored-detail-live.test.ts (2 tests | 1 failed) 1707ms
   × carries done("step_failed", { detail }) into the report, the journal and flows status 649ms
     → expected 'Flow "software-factory" declared done…' to be 'Flow "software-factory" declared done…' // Object.is equality

Restored (diff produced no output), rebuilt, re-run:

 ✓ tests/authored-completion-detail.test.ts (53 tests) 156ms
 ✓ tests/authored-detail-live.test.ts (2 tests) 3004ms
      Tests  55 passed (55)

git status --short after both mutations shows only the two untracked plan
files, so the committed tree is the restored one.


Test coverage, against the reviewed plan's regression matrix

Boundary Where
Surface / runtime packages/surface/tests/done-detail.test.ts (compiler: one-argument, {}, explicit undefined, all four lowered reasons, @ts-expect-error on a numeric detail, a bare-string options, an unknown option, a third argument, and a step-only reason)
Normalization authored-completion-detail.test.ts — five absence spellings, seven malformed shapes, type-named refusals with no value echoed, env-secret and token redaction, 1,999 / 2,000 / 2,001 code points, astral-plane counting, suffix inside the bound, straddling-secret ordering
Marker same file — the four no-detail literals; seven detail shapes executed through /bin/sh -c and parsed back (single quotes, double quotes, backslashes, newlines, $(id)/backticks, a '; rm …; echo ' terminator, unicode); the command is asserted to be one line
Spec stability same file — sixteen cases against PRE_CHANGE_SPEC (full canonical JSON and sha256)
Report same file — replacement message, byte-identical no-detail message, completionDetail absent when there is none, no fabricated step evidence, multiline-safe message with the original in detail, needs_human/declined/success exit codes and kinds unchanged
Root / IPC authored-root.test.ts — journaled output with and without the key, completed-root resume returning the stored detail with the body never run, legacy output, three malformed details failing closed. authored-node-result.test.ts — altered / added / dropped detail all refused; non-string, over-long and empty details refused at the frame
Local status authored-status-detail.test.ts — direct projection tests (nine negative cases including a similarly named ordinary step, retry, park, non-success root, malformed output), then CLI text and --json, full 2,000-code-point detail past the 1,024 gate limit, multiline folding, redaction in both renderings, and no line / no key for a one-argument done()
Cloud status cloud-read.test.ts — the real report message through a fake Cloud error to text and --json, a token redacted, and a finding in the middle of a 40-line detail surviving errorLines' elision
End to end authored-detail-live.test.ts — a real relayflowd: flows run --json exit 1 and message, the root's journaled output, the marker child's stdout_tail, flows status text and --json, flows resume returning the same detail with the journal byte-identical; plus the no-detail counterpart
Kernel relayflowd/src/server/tests.rs — persistence/reopen, idempotent admission, spec-drift refusal with a detail-bearing marker

What is not verified

Cloud's server-side projection. Issue item 3 asks to confirm that Cloud
stores the diagnostic message as the run error. This repository can show that
Cloud stores the CLI's JSON run report as the run result
(cloud-run.ts validates result.ok / .status / .completionReason) and
that flows status --cloud renders run.error (cli/cloud-read.ts). The step
that derives error from the report lives in agentrelay.com and is not in
this tree. The cloud tests here are labelled in the file as client contract
only
. No Cloud credential was available in this environment, so neither
flows status --cloud f92bf832-… nor a fresh hosted run on a rebuilt artifact
was attempted. The flows status --cloud acceptance criterion is therefore
not claimed as met end to end
— only that the message reaches a reader once
error holds it.

Pre-existing failures in this environment, unrelated to the change. The
full npm test in packages/sdk reports 7 failed files / 39 failed tests.
Every one of them was reproduced on the unmodified base commit:

$ git stash && cd packages/sdk && npx vitest run tests/canonical-software-factory.test.ts \
    tests/authored-node-runtime.test.ts tests/stuck-run-triage.test.ts \
    tests/provider-trigger-executor.test.ts tests/mcp.test.ts tests/webhook-live.test.ts \
    tests/live-kernel.test.ts
 Test Files  7 failed (7)
      Tests  39 failed | 50 passed | 18 skipped (107)
     Errors  1 error

— identical counts to the run with this change applied. Two causes, both
environmental: several suites hardcode kernel/target/debug/relayflowd instead
of reading RELAYFLOWD_BIN (which ops/cargo.sh relocates outside the repo,
and the file is ENOENT here), and examples/*.flow.ts resolves
@relayflows/surface to a stale 2.0.24 copy in a node_modules above the
repository root, which refuses the version header. npm run typecheck:examples in packages/surface likewise fails on
workflows/stuck-run-triage.flow.ts (Cannot find name 'URL') identically on
the base commit.

Follow-up (out of scope, per the ticket)

Issue item 4: the software-factory preset in agentrelay.com
web/lib/flow-workflows.ts should pass the reviewer's remaining findings — the
review.md summary or the P-level list — as the detail. Left unchanged here,
as were the two in-repo examples/software-factory callers:
tests/canonical-software-factory.test.ts pins that example's command
sequence, and feeding review.md into a detail would mean adding a journaled
f.run to a canonical example.

Rails

Committed on relayflow/flows-software-garden-abe21cfb, on top of 16237b6
(the merge of #544). Nothing is committed to main; a human merges. No gate
that judges this work was touched.

🤖 Generated with Claude Code


Note

Medium Risk
Touches authored execution, journal durability, IPC verification, and user-visible run/status reporting; behavior is heavily tested but resume/redaction ordering is subtle.

Overview
Adds done(reason, { detail }) so authored flows can explain verdicts (especially step_failed) instead of only generic failure text.

The SDK normalizes optional detail at done() (redact → bound to 2,000 Unicode code points with truncation suffix → strip lone surrogates), embeds it in the terminal marker JSON, and surfaces it as completionDetail on run reports, diagnostics, and flows status (authored done("…") beside unchanged kernel completed/success facts). Verdicts with detail are committed to a root authored-verdict stream before the marker run opens so resume cannot drift redaction and hit run_admission_conflict.

One-argument done() stays byte-identical for markers and spec hashes. Cloud docs/tests cover one-line error rendering when run.error carries the diagnostic message; server projection is explicitly out of repo scope. Kernel adds regression tests only (marker persistence, surrogate bad_request).

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


Summary by cubic

Lets an authored flow call done(reason, { detail }) so a step_failed verdict carries the actual finding instead of a generic sentence.

  • ctx.done(reason) still works unchanged; a missing, empty, or whitespace-only detail produces byte-identical markers, specs, hashes, reports, and JSON shapes.
  • The detail is normalized once at done(): redacted, trimmed, bounded to 2,000 Unicode code points including a … (truncated) suffix, and lone surrogates substituted with U+FFFD. Redaction runs before truncation, so cutting cannot split a secret into a fragment no pattern matches.
  • The verdict is committed to the root before the marker is opened, so a resumed body reuses the recorded detail instead of recomputing it — recomputation could drift if process.env changed while the process was down. A one-argument done() commits nothing.
  • The detail rides inside the terminal marker's deterministic command, making it a durable journal fact that the IPC verifier's existing command comparison attests for free — a frame cannot claim, drop, or alter a detail.
  • It surfaces as completionDetail and on the diagnostic's detail; flows status prints a labelled authored done("...") line beside the kernel facts, which stay completed / success.
  • Kernel has no production change; a regression test pins that a detail-bearing marker survives journal reopen, is idempotently admitted, and is refused on drift.

To review

  • Cloud's server-side projection of the report message into a run's error lives in agentrelay.com and is not in this tree, so the flows status --cloud acceptance criterion is only verified client-side.

Written for commit 43a3b22. Summary will update on new commits.

Review in cubic

`ctx.done(reason)` took only the reason, so every authored `step_failed`
landed on one generic sentence — "No step failed, so there is no step-level
evidence to inspect". Cloud run f92bf832 finished that way after twenty
successful steps and an opened PR; the reviewer's actual finding ("one P2
remains: `review.clean` was not created") existed and reached no reader.
Finding it took opening the Codex reviewer's raw transcript.

`done()` now takes an optional `{ detail }`. It is normalized once, at
`done()`: redacted with the SDK's existing redactor, trimmed, and bounded to
2,000 Unicode code points including a fixed `… (truncated)` suffix. Redaction
runs BEFORE truncation, because cutting a secret in half leaves nothing for an
exact-match redaction to find — a truncation that causes the leak.

The detail travels inside the terminal marker's deterministic command, so it
is a durable journal fact and the IPC verifier's existing command comparison
attests it for free. It is carried on the authored root's output, read back by
a completed-root resume without recomputing it, and reported as
`completionDetail` plus the diagnostic's `detail`; for `step_failed` it
replaces the generic sentence. `flows status` prints it as a labelled
`authored done("...")` line beside — never instead of — the kernel's own
account, which stays `completed / success` because that is what happened.

No detail means no change: the marker command, the lowered spec, its canonical
hash, the report message and every JSON shape are byte-identical, pinned
against fixtures captured from the pre-change tree.

Kernel: no production change. The detail is opaque data in a shell string. A
regression pins the three kernel properties the SDK relies on — the marker
command survives journal persistence and reopen verbatim, re-admitting it
under the same admission key returns the same run, and changed marker data is
refused as `run_admission_conflict`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 69616f3a-8180-4eca-ad2d-e20e5f0fe56d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

…rogates

Two P2 findings from the review of #547.

**F1 — a committed detail is recovered, not recomputed.** The terminal
marker's command embeds the detail and the marker run is opened under a
stable admission key, but normalization redacts against `process.env`. A
credential rotated or removed while the process was down made the SAME
authored sentence normalize differently, so a resumed body retried the
marker's key with a drifted spec and the kernel refused it as
`run_admission_conflict` — losing an explanation the root had already
journaled, for a flow whose every step succeeded.

The verdict is now appended to a stream on the root BEFORE the marker run is
opened, and a later attempt reaching the same terminal step reuses what was
recorded. That is the durability the executor already gives a predicate gate:
record the decision first, so the spec built from it is identical on every
attempt. A `done()` with no detail commits nothing — its marker command is a
function of the reason alone, nothing can drift, and that flow's journal is
byte-for-byte the one it always wrote.

**F2 — a lone surrogate never reaches a durable write.** `(prose +
'\u{1F642}').slice(0, -1)` is ordinary JS trimming of an agent's output and
produces a `string` that is not text. `JSON.stringify` escapes it, so the
marker command was admitted; the raw detail then went out in the root's
`step.complete` output, where the kernel's JSON decoder answered `bad_request`
with a null request id — which resolves no pending request, so the CLI
produced no report at all. Normalization now substitutes U+FFFD, one code
unit for one, and `isDurableCompletionDetail` enforces the same invariant at
every read boundary. Substituting rather than refusing: the flow has already
reached its verdict, and losing the whole explanation over one broken code
unit destroys more than it protects.

Docs: `docs/CLOUD.md` no longer states the server's report-to-`error`
projection as established fact. What this repo pins is the client half — the
message this CLI produces and the rendering `flows status --cloud` gives an
`error` that holds it; the hosted end to end is named as unconfirmed.

Kernel: still no production change. One test pins the decoder property the
SDK's normalization exists for — a lone surrogate is refused at the line, and
the refusal carries `"id": null`, so no pending request can be matched to it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@agent-relay-code

Copy link
Copy Markdown
Contributor Author

Relayflow: the adversarial review did not pass. This branch is not approved: the flow stopped here and did not mark it ready to merge.

Review of PR #547: changes requested — one P2 finding remains.

Reviewed head: 43a3b227d0f3bb0a13bc5aa525e23c88bcb159dc, against base 16237b6. Reviewed the full change, the follow-up commit, affected tests, PR description, and all available PR comments/reviews. The only issue comment is CodeRabbit's “Review skipped”; there are no inline comments or submitted reviews. This is a local review; no GitHub comment was posted.

[P2] Recover a committed verdict before taking the no-detail shortcut.

Location: packages/sdk/src/authored-completion-record.ts:58.

commitAuthoredVerdict returns immediately when this attempt's detail is undefined, before reading the root's committed verdict. That confuses “this attempt has no detail” with “this root has never recorded a detail.” For example, a flow can supply an optional review summary from its environment; after a worker/sandbox restart that optional source may be absent. At the same terminal step, an already committed explanation must remain authoritative, just as it does when the retry supplies different nonempty text.

The real-daemon probe below uses the same flow, root identity, and reason on both executions. Its first attempt journals review found 1 P2: cleanup remains ambiguous, then loses the marker response. Its retry supplies { detail: undefined } because the optional environment value is no longer present. Both failure windows reproduce:

  • Loss before marker admission: retry completes without completionDetail, silently abandoning the explanation already in the root stream.
  • Loss after marker admission: retry rebuilds the marker without detail and rejects with run_admission_conflict instead of recovering the recorded explanation.

Move the root-stream lookup ahead of the current-attempt no-detail shortcut. When a record exists, recover it; when none exists and this attempt has no detail, return without appending. This preserves no-detail marker bytes, spec hashes, and journal writes while honoring committed verdicts. Add regressions for both windows, including undefined/blank retry detail. The probe changes only the optional source, not the flow code or root identity; it is executor-seam failure injection, not a process-kill test.

The prior review's lone-surrogate bug and its original same-text credential-rotation case are addressed by the follow-up code and the passing regressions below. The remaining finding is a different branch in that recovery logic.

Cloud acceptance is still limited to the client contract: cloud-read.test.ts injects the report diagnostic as a mocked run's error, then tests rendering. It does not establish the server's diagnostic-to-error projection or a hosted run. The revised documentation now states that limitation accurately. This review makes no claim that Cloud drops the message or that hosted end-to-end acceptance was verified.

review.clean was not created. No production files, existing tests, generated files, or docs/evidence files were edited by this review. The temporary probe was removed after execution; its complete source and literal output are below. No mutation verification is claimed.

Verification evidence

The surface suite, 22 affected SDK suites, SDK build/typechecks, and kernel library suite were run. This is not a claim of running the full SDK suite. Commands and captured outputs follow.

Working directory: packages/surface

npm test > /tmp/review547-surface.log 2>&1; result=$?; cat /tmp/review547-surface.log; exit "$result"

> @relayflows/surface@2.0.25 test
> bun run build && tsc -p tsconfig.test.json && vitest run

$ tsc

 RUN  v2.1.9 /home/daytona/.relayflow-v2-supervisor/durable/repository/packages/surface

 ✓ tests/slack-block-kit.test.ts (5 tests) 5ms
 ✓ tests/flow.test.ts (24 tests) 13ms
 ✓ tests/provider-triggers.test.ts (3 tests) 5ms
 ✓ tests/triggers-all-providers.test.ts (4 tests) 43ms
 ✓ tests/triggers.test.ts (4 tests) 44ms
 ✓ tests/done-detail.test.ts (1 test) 2ms
 ✓ tests/triggers-github-events.test.ts (1 test) 3ms
 ✓ tests/declined.test.ts (1 test) 2ms
 ✓ tests/helpers.snapshot.test.ts (1 test) 367ms
   ✓ regenerates helpers byte-identically from the pinned adapter 367ms
 ✓ tests/schedule.test.ts (7 tests) 9971ms
   ✓ schedule.cron > measures a cron's longest quiet period so a silence budget can be declared honestly 9947ms

 Test Files  10 passed (10)
      Tests  51 passed (51)
   Start at  23:01:18
   Duration  10.60s (transform 825ms, setup 0ms, collect 2.11s, tests 10.46s, environment 1ms, prepare 849ms)

Exit code: 0.

Working directory: packages/sdk

npm run build && npm run typecheck && npm run typecheck:tests

> @relayflows/sdk@2.0.25 build
> tsc && node scripts/make-cli-executable.mjs


> @relayflows/sdk@2.0.25 typecheck
> tsc --noEmit && tsc -p tsconfig.type-tests.json


> @relayflows/sdk@2.0.25 typecheck:tests
> tsc -p tsconfig.tests.json

Exit code: 0.

npx vitest run tests/authored-completion-detail.test.ts tests/authored-completion-recovery.test.ts tests/authored-status-detail.test.ts tests/authored-detail-live.test.ts tests/authored-root.test.ts tests/authored-node-result.test.ts tests/cloud-read.test.ts tests/authored-step-failed.test.ts tests/authored-declined.test.ts tests/authored-declined-report.test.ts tests/authored-declined-live.test.ts tests/authored-step-failed-exit.test.ts tests/cli-status.test.ts tests/run-state.test.ts tests/cloud-run.test.ts tests/spec-parity.test.ts tests/authored-flow.test.ts tests/authored-human.test.ts tests/authored-admission.test.ts tests/cli.test.ts tests/direct-run-failure.test.ts tests/authored-run-failure-evidence.test.ts > /tmp/review547-sdk.log 2>&1; result=$?; cat /tmp/review547-sdk.log; exit "$result"

 RUN  v2.1.9 /home/daytona/.relayflow-v2-supervisor/durable/repository/packages/sdk

 ✓ tests/cloud-read.test.ts (42 tests) 41ms
(node:68381) ExperimentalWarning: SQLite is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
 ✓ tests/cloud-run.test.ts (58 tests) 778ms
 ✓ tests/cli.test.ts (65 tests) 1530ms
   ✓ flows check CLI > binds a checked relative wrapper to the flow directory for worker execution 430ms
 ✓ tests/authored-completion-detail.test.ts (59 tests) 134ms
 ✓ tests/cli-status.test.ts (26 tests) 1091ms
   ✓ flows status > resolves the run with no arguments from inside a worker-spawned agent 829ms
 ✓ tests/run-state.test.ts (21 tests) 11ms
 ✓ tests/authored-node-result.test.ts (42 tests) 20ms
 ✓ tests/authored-root.test.ts (20 tests) 172ms
 ✓ tests/authored-human.test.ts (13 tests) 80ms
 ✓ tests/authored-flow.test.ts (25 tests) 800ms
(node:68739) ExperimentalWarning: SQLite is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
 ✓ tests/authored-status-detail.test.ts (20 tests) 81ms
 ✓ tests/authored-step-failed.test.ts (10 tests) 35ms
 ✓ tests/authored-run-failure-evidence.test.ts (8 tests) 893ms
   ✓ the child index after the process that wrote it is gone > still names every child, with its own run id, after a daemon restart 498ms
 ✓ tests/spec-parity.test.ts (31 tests) 406ms
 ✓ tests/authored-completion-recovery.test.ts (3 tests) 293ms
 ✓ tests/authored-declined.test.ts (13 tests) 49ms
 ✓ tests/authored-step-failed-exit.test.ts (3 tests) 12ms
 ✓ tests/direct-run-failure.test.ts (8 tests) 16ms
 ✓ tests/authored-declined-report.test.ts (6 tests) 7ms
 ✓ tests/authored-admission.test.ts (2 tests) 3ms
 ✓ tests/authored-declined-live.test.ts (1 test) 1691ms
   ✓ runs an input guard and resumes its completed declined root without repeated effects 1690ms
 ✓ tests/authored-detail-live.test.ts (3 tests) 3777ms
   ✓ carries done("step_failed", { detail }) into the report, the journal and flows status 2131ms
   ✓ reports a detail a caller sliced through an emoji, instead of hanging on it 632ms
   ✓ leaves a one-argument done("step_failed") reporting exactly as it always did 1013ms

 Test Files  22 passed (22)
      Tests  479 passed (479)
   Start at  23:01:54
   Duration  9.35s (transform 1.45s, setup 0ms, collect 10.94s, tests 11.92s, environment 3ms, prepare 974ms)

Exit code: 0.

Working directory: kernel

sh ../ops/cargo.sh test -p relayflowd --lib > /tmp/review547-kernel.log 2>&1; result=$?; cat /tmp/review547-kernel.log; exit "$result"
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.08s
     Running unittests src/lib.rs (/home/daytona/.relayflows-toolchain/target/2962130851/debug/deps/relayflowd-f043db0bb3534a16)

running 55 tests
test engine::remote::worker_failure_detail_tests::a_non_string_output_is_rendered_rather_than_dropped ... ok
test engine::remote::worker_failure_detail_tests::a_null_or_blank_output_yields_no_detail ... ok
test engine::remote::worker_failure_detail_tests::a_string_output_is_carried_verbatim_and_trimmed ... ok
test engine::remote::worker_failure_detail_tests::an_output_at_the_boundary_is_not_truncated ... ok
test engine::boot_identity_tests::every_engine_in_this_process_shares_one_boot_id ... ok
test engine::remote::worker_failure_detail_tests::render_is_bounded_before_allocation_for_hostile_string ... ok
test engine::remote::worker_failure_detail_tests::truncation_does_not_split_a_multi_byte_char ... ok
test engine::wake::claim_guard_tests::a_disarmed_guard_leaves_the_claim_alone ... ok
test engine::remote::worker_failure_detail_tests::render_is_bounded_before_allocation_for_hostile_json ... ok
test engine::wake::claim_guard_tests::a_guard_only_releases_its_own_run ... ok
test engine::wake::claim_guard_tests::a_panic_between_claim_and_register_still_releases ... ok
test exec_det::tests::captures_deterministic_output ... ok
test exec_det::tests::failed_command_evidence_survives_completion ... ok
test engine::boot_identity_tests::prior_boot_registered_undriven_admission_is_recovered_by_start_retry ... ok
test engine::wake::claim_guard_tests::an_armed_guard_releases_the_claim_when_dropped ... ok
test exec_det::tests::timeout_has_an_explicit_completion_reason ... ok
test server::channels::tests::unknown_verb_never_falls_through_to_receive ... ok
test server::client::tests::resume_waits_while_the_heartbeat_renewed_lease_is_live ... ok
test server::liveness::tests::sweep_id_buckets_by_the_interval ... ok
test exec_det::tests::lease_override_bounds_execution_and_preserves_command_timeout ... ok
test server::liveness::tests::sweep_pass_healthy_subscription_is_a_noop ... ok
test server::liveness::tests::sweep_pass_latches_after_journaling_and_next_bucket_is_empty ... ok
test server::tests::a_lone_surrogate_in_a_request_is_refused_with_no_request_id ... ok
test engine::boot_identity_tests::failure_after_workspace_binding_releases_admission_without_exposing_effects ... ok
test server::tests::a_failed_disconnect_journal_append_is_retained_and_retried_not_dropped ... ok
test server::tests::agent::contract::a_transcript_digest_at_its_budget_rides_trajectory_tail_verbatim ... ok
test server::tests::agent::contract::a_replacement_worker_that_never_reported_the_pinned_surface_is_not_dispatched_to ... ok
test server::tests::agent::contract::an_agent_worker_attaching_without_pins_is_refused_at_attach ... ok
test server::tests::agent::contract::agent_without_a_compatible_worker_parks_without_starting ... ok
test server::tests::agent::contract::an_oversized_trajectory_tail_is_refused_at_step_complete ... ok
test server::tests::agent::contract::an_agent_worker_missing_a_declared_surface_parks_the_run_instead_of_erroring ... ok
test server::tests::agent::contract::an_llm_completion_claiming_an_effect_fails_closed_with_the_reason_journaled ... ok
test server::tests::agent::eligibility::required_streams_must_be_held_before_worker_registration ... ok
test server::tests::agent::eligibility::required_streams_keep_ordinary_steps_off_conversation_workers ... ok
test server::tests::agent::contract::human_intervention_is_durable_and_resume_requires_explicit_override ... ok
test server::tests::agent::pins::reset_worker_reporting_a_revision_other_than_its_pin_fails_closed_as_worker_error ... ok
test server::tests::agent::pins::consecutive_agent_steps_on_different_surfaces_each_start_from_their_own_pins ... ok
test server::tests::deterministic_marker_carrying_json_survives_reopen_and_refuses_changed_detail ... ok
test server::tests::hello_enforces_protocol_version ... ok
test server::tests::run_resume_adopts_a_real_journal_whose_registry_row_is_missing ... ok
test server::tests::run_resume_asks_the_registry_instead_of_treating_an_orphan_file_as_a_run ... ok
test server::tests::run_resume_refuses_a_journal_that_never_recorded_its_run ... ok
test server::tests::run_resume_refuses_a_valid_journal_that_belongs_to_another_run ... ok
test server::tests::run_start_admission_key_recovers_the_same_run_and_refuses_spec_drift ... ok
test server::tests::run_start_fails_closed_on_an_unknown_verification_key ... ok
test server::tests::run_start_refuses_invalid_admission_keys ... ok
test exec_det::tests::timeout_kills_the_whole_process_group ... ok
test server::tests::step_wait_parks_the_attempt_and_a_human_answer_redispatches_it ... ok
test socket_path::tests::deep_data_dir_produces_short_socket_path ... ok
test socket_path::tests::different_data_dirs_yield_different_sockets ... ok
test socket_path::tests::relative_and_absolute_data_dirs_agree ... ok
test socket_path::tests::same_data_dir_yields_same_socket ... ok
test server::tests::stopped_heartbeats_past_the_deadline_journal_lease_expired_and_release_the_step ... ok
test server::tests::agent::pins::a_replacement_worker_at_a_different_revision_is_not_dispatched_the_stale_pins ... ok
test server::tests::an_entry_appended_during_watch_registration_is_delivered_exactly_once ... ok

test result: ok. 55 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.65s

Exit code: 0.

Remaining finding: reproduction and captured failure

Save the following as packages/sdk/tests/zz-review547-recovery.test.ts. The exact probe is also retained at /tmp/review547-recovery.test.ts; it was removed from the package after execution.

import { expect, it } from 'vitest';
import { flow } from '@relayflows/surface';
import { executeAuthoredFlow } from '../src/authored-flow-executor.js';
import { readAuthoredVerdict } from '../src/authored-completion-record.js';
import { chainFixture } from './flow-chain-fixture.js';

it.each([false, true])('recovers a committed explanation when the optional source disappears; marker admitted=%s', async (admitMarker) => {
  const fixture = chainFixture();
  const envName = 'REVIEW_SUMMARY';
  const previous = process.env[envName];
  try {
    const journal = await fixture.connect();
    const root = await journal.runStart({ name: 'recovery-root', steps: [{
      id: 'authored-root', type: 'agent', instruction: '{}',
      surfaces: { streams: [{ stream: 'unheld-root-stream' }] }, recovery_mode: 'reset', max_iterations: 8,
    }] } as never);
    const handle = flow('optional-review-summary', async f => {
      f.done('step_failed', { detail: process.env[envName] });
    });
    const run = () => executeAuthoredFlow(handle, journal, undefined, { rootRunId: root.run_id, dataDir: fixture.data });
    process.env[envName] = 'review found 1 P2: cleanup remains ambiguous';
    const start = journal.runStart.bind(journal);
    journal.runStart = async (...args) => {
      if (admitMarker) await start(...args);
      throw new Error('injected marker response loss');
    };
    await expect(run()).rejects.toThrow('injected marker response loss');
    journal.runStart = start;
    const committed = await readAuthoredVerdict(journal, root.run_id, 'complete-1');
    console.log('committed before retry', JSON.stringify(committed));
    delete process.env[envName];
    await expect(run()).resolves.toMatchObject({ completionReason: 'step_failed', completionDetail: committed!.detail });
  } finally {
    if (previous === undefined) delete process.env[envName]; else process.env[envName] = previous;
    await fixture.close();
  }
}, 60000);

Working directory: packages/sdk

npx vitest run tests/zz-review547-recovery.test.ts > /tmp/review547-probe.log 2>&1; result=$?; cat /tmp/review547-probe.log; exit "$result"

 RUN  v2.1.9 /home/daytona/.relayflow-v2-supervisor/durable/repository/packages/sdk

stdout | tests/zz-review547-recovery.test.ts > recovers a committed explanation when the optional source disappears; marker admitted=false
committed before retry {"reason":"step_failed","detail":"review found 1 P2: cleanup remains ambiguous"}

stdout | tests/zz-review547-recovery.test.ts > recovers a committed explanation when the optional source disappears; marker admitted=true
committed before retry {"reason":"step_failed","detail":"review found 1 P2: cleanup remains ambiguous"}

 ❯ tests/zz-review547-recovery.test.ts (2 tests | 2 failed) 748ms
   × recovers a committed explanation when the optional source disappears; marker admitted=false 662ms
     → expected { …(4) } to match object { …(2) }
(3 matching properties omitted from actual)
   × recovers a committed explanation when the optional source disappears; marker admitted=true 85ms
     → promise rejected "JournalProtocolError: run_admission_confl… { code: '…' }" instead of resolving

⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯

 FAIL  tests/zz-review547-recovery.test.ts > recovers a committed explanation when the optional source disappears; marker admitted=false
AssertionError: expected { …(4) } to match object { …(2) }
(3 matching properties omitted from actual)

- Expected
+ Received

  Object {
-   "completionDetail": "review found 1 P2: cleanup remains ambiguous",
    "completionReason": "step_failed",
  }

 ❯ tests/zz-review547-recovery.test.ts:32:5
     30|     console.log('committed before retry', JSON.stringify(committed));
     31|     delete process.env[envName];
     32|     await expect(run()).resolves.toMatchObject({ completionReason: 'st…
       |     ^
     33|   } finally {
     34|     if (previous === undefined) delete process.env[envName]; else proc…

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯

 FAIL  tests/zz-review547-recovery.test.ts > recovers a committed explanation when the optional source disappears; marker admitted=true
AssertionError: promise rejected "JournalProtocolError: run_admission_confl… { code: '…' }" instead of resolving
 ❯ tests/zz-review547-recovery.test.ts:32:23
     30|     console.log('committed before retry', JSON.stringify(committed));
     31|     delete process.env[envName];
     32|     await expect(run()).resolves.toMatchObject({ completionReason: 'st…
       |                       ^
     33|   } finally {
     34|     if (previous === undefined) delete process.env[envName]; else proc…

Caused by: JournalProtocolError: run_admission_conflict: run admission key "authored-child:ab5d5ff34f0d41a6e06550d8ad50d05c5764d22a2261f0cbd97fcf89f105df21" is already bound to a different spec
 ❯ JournalClient.onLine src/journal-client.ts:150:27
 ❯ JournalClient.onData src/journal-client.ts:129:33
 ❯ Socket.<anonymous> src/journal-client.ts:107:43

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'run_admission_conflict' }
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯

 Test Files  1 failed (1)
      Tests  2 failed (2)
   Start at  23:02:22
   Duration  2.19s (transform 744ms, setup 0ms, collect 1.27s, tests 748ms, environment 0ms, prepare 46ms)

Exit code: 1.

PR comments inspected

gh api --paginate repos/AgentWorkforce/flows/pulls/547/comments > /tmp/review547-inline-comments.json
gh api --paginate repos/AgentWorkforce/flows/issues/547/comments > /tmp/review547-issue-comments.json
gh api --paginate repos/AgentWorkforce/flows/pulls/547/reviews > /tmp/review547-reviews.json

Captured inline-comment response:

[]

Captured submitted-review response:

[]

Captured issue-comment response:

[{"url":"https://api.github.com/repos/AgentWorkforce/flows/issues/comments/5768503296","html_url":"https://github.com/AgentWorkforce/flows/pull/547#issuecomment-5768503296","issue_url":"https://api.github.com/repos/AgentWorkforce/flows/issues/547","id":5768503296,"node_id":"IC_kwDOUF0ysM8AAAABV9RgAA","user":{"login":"coderabbitai[bot]","id":136622811,"node_id":"BOT_kgDOCCSy2w","avatar_url":"https://avatars.githubusercontent.com/in/347564?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai%5Bbot%5D","html_url":"https://github.com/apps/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/followers","following_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/repos","events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false},"created_at":"2026-09-21T22:39:53Z","updated_at":"2026-09-21T23:00:20Z","body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- This is an auto-generated comment: skip review by coderabbit.ai -->\n\n> [!IMPORTANT]\n> ## Review skipped\n> \n> Bot user detected.\n> \n> To trigger a single review, invoke the `@coderabbitai review` command.\n> \n> <details>\n> <summary>⚙️ Run configuration</summary>\n> \n> **Configuration used**: Organization UI\n> \n> **Review profile**: CHILL\n> \n> **Plan**: Advanced\n> \n> **Run ID**: `69616f3a-8180-4eca-ad2d-e20e5f0fe56d`\n> \n> </details>\n> \n> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.\n> \n> Use the checkbox below for a quick retry:\n> - [ ] <!-- {\"checkboxId\":\"e9bb8d72-00e8-4f67-9cb2-caf3b22574fe\"} --> 🔍 Trigger review\n\n<!-- end of auto-generated comment: skip review by coderabbit.ai -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=AgentWorkforce/flows&utm_content=547)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands.</sub>\n\n<!-- tips_end -->","author_association":"NONE","reactions":{"url":"https://api.github.com/repos/AgentWorkforce/flows/issues/comments/5768503296/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":{"id":347564,"client_id":"Iv1.6aaafe4fe882736b","slug":"coderabbitai","node_id":"A_kwHOB96YWc4ABU2s","owner":{"login":"coderabbitai","id":132028505,"node_id":"O_kgDOB96YWQ","avatar_url":"https://avatars.githubusercontent.com/u/132028505?v=4","gravatar_id":"","url":"https://api.github.com/users/coderabbitai","html_url":"https://github.com/coderabbitai","followers_url":"https://api.github.com/users/coderabbitai/followers","following_url":"https://api.github.com/users/coderabbitai/following{/other_user}","gists_url":"https://api.github.com/users/coderabbitai/gists{/gist_id}","starred_url":"https://api.github.com/users/coderabbitai/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coderabbitai/subscriptions","organizations_url":"https://api.github.com/users/coderabbitai/orgs","repos_url":"https://api.github.com/users/coderabbitai/repos","events_url":"https://api.github.com/users/coderabbitai/events{/privacy}","received_events_url":"https://api.github.com/users/coderabbitai/received_events","type":"Organization","user_view_type":"public","site_admin":false},"name":"coderabbitai","description":"# Transforming Code Reviews with AI\r\n\r\n## Features\r\n\r\n**Automated Reviews**: Continuous reviews of the pull requests including incremental commits. \r\n\r\n**Summarization**: Generates high-level summary and a technical walkthrough of the PR changes. \r\n\r\n**Line-by-line review**: Provides line-by-line suggestions committable with one click.\r\n\r\n**Codebase verification**:  Verifies the impact on the overall codebase and identifies missing changes.\r\n\r\n**Insights into your code**:  Ask any questions on your codebase within the pull request \r\n\r\n**Chat about your code** : Chat with the bot around your code. The more you chat, the smarter it gets.\r\n\r\n**Issue Validation**:  Validates the PR against the linked issues and identifies other related issues \r\n\r\n\r\n\r\n","external_url":"https://coderabbit.ai?utm_source=cr_app&utm_medium=github","html_url":"https://github.com/apps/coderabbitai","created_at":"2023-06-14T15:47:27Z","updated_at":"2026-09-20T03:49:19Z","permissions":{"actions":"read","checks":"write","contents":"write","discussions":"read","issues":"write","members":"read","merge_queues":"read","metadata":"read","pull_requests":"write","statuses":"write"},"events":["issues","issue_comment","label","membership","merge_group","organization","pull_request","pull_request_review","pull_request_review_comment","pull_request_review_thread","release","repository","team"]},"minimized":null}]

@agent-relay-code
agent-relay-code Bot marked this pull request as draft September 21, 2026 23:04
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.

0 participants