Skip to content

feat: migrate agents to @relayfile/relay-helpers#20

Merged
khaliqgant merged 1 commit into
mainfrom
feat/migrate-to-relay-helpers
Jun 2, 2026
Merged

feat: migrate agents to @relayfile/relay-helpers#20
khaliqgant merged 1 commit into
mainfrom
feat/migrate-to-relay-helpers

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jun 2, 2026

Copy link
Copy Markdown
Member

User description

Migrates all agents off the hardcoded VFS-path writes to the ergonomic, catalog-backed clients from @relayfile/relay-helpers (0.3.29). Paths now resolve from the adapter catalog instead of string literals duplicated in each agent — so a provider path change in relayfile-adapters flows through automatically.

Agent Before After
hn-monitor, vendor-monitor writeJsonFile(c,'slack','post','/slack/channels/…',…) slackClient().post(channel, text)
spotify-releases slack DM path slackClient().dm(user, text)
linear readJsonFile/writeJsonFile linear paths linearClient().getIssue(id) / .comment(id, body)
granola linear create+comment (needs receipt) relayClient('linear').write('issues'|'comments', …)
review github comment/merge + slack githubClient().comment(…) / .mergePullRequest(…) + slackClient().post(…)
repo-hygiene github comment + slack githubClient().comment(…) + slackClient().post(…)

Reads that aren't catalog writeback resources stay on the raw runtime VFS helpers (correct — they're not in the catalog): github pulls/meta.json + the Notion journal (repo-hygiene), the linear teams listing (granola).

Verified the github merge payload contract while migrating: the adapter reads object.method ?? object.merge_method (and snake/camel for commit fields), so mergePullRequest is compatible.

npm run typecheck clean.

Note: left an unrelated pre-existing working-tree change in review/persona.ts (a removed Daytona comment) out of this PR since I didn't author it.

🤖 Generated with Claude Code


Summary by cubic

Migrate all agents from hardcoded VFS write paths to catalog-backed clients in @relayfile/relay-helpers for cleaner APIs and automatic adapter path updates. Slack, Linear, and GitHub writebacks now use typed helpers; non-catalog reads are unchanged.

  • Refactors

    • Slack: slackClient().post(channel, text) / .dm(user, text) in hn-monitor, vendor-monitor, spotify-releases, review, repo-hygiene.
    • Linear: linearClient().getIssue(id) / .comment(id, body); granola uses relayClient('linear').write('issues'|'comments', …) to keep writeback receipts.
    • GitHub: githubClient().comment(...) and .mergePullRequest(...) in review and repo-hygiene (merge response validated via merged).
    • Reads not in the adapter catalog stay on runtime VFS (e.g., GitHub pulls/meta.json, Notion journal, Linear teams).
  • Dependencies

    • Add @relayfile/relay-helpers@^0.3.29 (pulls @relayfile/adapter-core@^0.3.29).
    • Housekeeping: .gitignore adds .claude/; npm run typecheck clean.

Written for commit 62d77a1. Summary will update on new commits.

Review in cubic


CodeAnt-AI Description

Move agent Slack, GitHub, and Linear actions to relay helper clients

What Changed

  • Agents now send Slack DMs and channel posts through the relay helper clients instead of writing to VFS paths directly
  • Linear issue lookup, issue creation, and issue comments now use the helper clients
  • PR review and merge actions now use the GitHub helper client, including merge confirmation before reporting success
  • Added the relay helper package to the project dependencies

Impact

✅ Fewer broken integration paths
✅ More reliable Slack, GitHub, and Linear actions
✅ Safer PR merge confirmation

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Replaces the hardcoded VFS-path writes with the ergonomic, catalog-backed
clients from @relayfile/relay-helpers — paths now come from the adapter catalog
instead of string literals in each agent.

- slack post/dm  -> slackClient().post(channel,text) / .dm(user,text)
  (hn-monitor, vendor-monitor, spotify-releases, review, repo-hygiene)
- linear get/comment -> linearClient().getIssue(id) / .comment(id,body)  (linear)
- linear create+comment needing the writeback receipt -> relayClient('linear')
  .write('issues'|'comments', ...)  (granola)
- github comment / merge -> githubClient().comment(...) / .mergePullRequest(...)
  (review, repo-hygiene)

Reads that aren't catalog writeback resources stay on the raw runtime VFS
helpers: github pulls/meta.json + notion journal (repo-hygiene), linear teams
(granola). typecheck clean.

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

codeant-ai Bot commented Jun 2, 2026

Copy link
Copy Markdown

CodeAnt AI is reviewing your PR.

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors all agent handlers to replace VFS-based integration writes with direct client calls from @relayfile/relay-helpers. The new dependency is added to package.json, imports are updated across seven agents to swap VFS helpers for typed relay clients, and each agent's integration logic is rewritten to use linearClient(), slackClient(), and githubClient() instead of writeJsonFile/readJsonFile operations.

Changes

Relay helpers client migration

Layer / File(s) Summary
Dependency and import refactoring
package.json, granola/agent.ts, hn-monitor/agent.ts, linear/agent.ts, repo-hygiene/agent.ts, review/agent.ts, spotify-releases/agent.ts, vendor-monitor/agent.ts
@relayfile/relay-helpers is added as a dependency. All agents remove imports of VFS helpers (writeJsonFile, draftFile, encodeSegment, resolveMountRoot) and add relay client factory imports (relayClient, linearClient, slackClient, githubClient).
Linear client integration
linear/agent.ts, granola/agent.ts
linear/agent.ts removes the local vfsClient() helper and adopts linearClient() to fetch issues via getIssue() and post comments via comment(). granola/agent.ts uses relayClient('linear') to create issues and post PR comments via write() calls.
GitHub and Slack client integration
review/agent.ts, repo-hygiene/agent.ts, hn-monitor/agent.ts, spotify-releases/agent.ts, vendor-monitor/agent.ts
review/agent.ts adopts githubClient() for PR comments and merge operations, and slackClient() for review notifications. repo-hygiene/agent.ts uses both clients for PR updates and Slack summaries. Remaining agents adopt slackClient() for direct Slack posting and DM delivery.
Repository configuration
.gitignore
.claude/ directory is added to .gitignore to exclude local Claude workspace artifacts from version control.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • AgentWorkforce/agents#10: review/agent.ts failure handling path overlaps with refactoring of GitHub/Slack writeback for error reporting.

  • AgentWorkforce/agents#7: repo-hygiene/agent.ts GitHub/Slack writeback refactoring builds on the agent structure introduced in that PR.

  • AgentWorkforce/agents#19: Similar refactoring of agent writeback logic across Linear, Slack, and GitHub integrations but moving from direct client calls to VFS approach, opposite direction of this PR.

Poem

🐰 From VFS paths we now break free,
Direct clients call with clarity,
Each agent sings a simpler song,
Relay helpers make them strong,
No more drafts, just send it right!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: migrating agents to use @relayfile/relay-helpers library instead of hardcoded VFS paths.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the migration from VFS-path writes to catalog-backed clients with concrete before/after examples and implementation details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/migrate-to-relay-helpers

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 and usage tips.

@codeant-ai codeant-ai Bot added the size:M This PR changes 30-99 lines, ignoring generated files label Jun 2, 2026

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request refactors several agents to use helper clients from @relayfile/relay-helpers (such as slackClient, linearClient, githubClient, and relayClient) instead of manually constructing paths and calling writeJsonFile or readJsonFile from @agentworkforce/runtime. Feedback on the changes highlights a potential runtime crash in linear/agent.ts if the issue is not found, suggesting a defensive check before processing the retrieved issue.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread linear/agent.ts
'getIssue',
`/linear/issues/${encodeSegment(issueId)}.json`
);
const issue = await linear.getIssue<LinearIssue>(issueId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To prevent potential runtime crashes, we should defensively check if the issue was successfully retrieved before passing it to parseRepo. If the issue is not found or the API call returns undefined/null, calling parseRepo(issue) will throw a TypeError when attempting to access issue.title.

Suggested change
const issue = await linear.getIssue<LinearIssue>(issueId);
const issue = await linear.getIssue<LinearIssue>(issueId);
if (!issue) {
ctx.log('warn', 'linear-implementer.issue-not-found', { issueId });
return;
}

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.

Declining: linearClient().getIssue<LinearIssue>(issueId) returns a non-nullable LinearIssue — on a missing/unreadable file it throws (readJsonFileRelayfileWritebackError), it never resolves to undefined/null. So if (!issue) is dead code, and the behavior is unchanged from the pre-migration readJsonFile<LinearIssue>(...) which threw the same way.

@codeant-ai

codeant-ai Bot commented Jun 2, 2026

Copy link
Copy Markdown

CodeAnt AI finished reviewing your PR.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@review/agent.ts`:
- Around line 98-99: Ensure GitHub and Slack writes are idempotent by adding a
dedupe guard before calling githubClient().comment(...) and
slackClient().post(...): generate a stable idempotency key per PR/run (e.g.,
using pr.owner, pr.repo, pr.number and the review run id) and either pass it to
the client if it supports idempotency headers or check-and-skip by querying
existing PR comments / Slack messages for that key; apply the same guard in
failReviewRun() and mergePr() so they first verify whether the comment/post with
that idempotency key already exists before posting to avoid duplicate writes on
retries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 04e1d687-d95f-47ce-a994-781a84210528

📥 Commits

Reviewing files that changed from the base of the PR and between 02f6216 and 62d77a1.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • .gitignore
  • granola/agent.ts
  • hn-monitor/agent.ts
  • linear/agent.ts
  • package.json
  • repo-hygiene/agent.ts
  • review/agent.ts
  • spotify-releases/agent.ts
  • vendor-monitor/agent.ts

Comment thread review/agent.ts
Comment on lines +98 to 99
await githubClient().comment({ owner: pr.owner, repo: pr.repo, number: pr.number }, body);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

Does @relayfile/relay-helpers githubClient/slackClient provide idempotency keys or dedupe for comment/post calls?

💡 Result:

No evidence found that @relayfile/relay-helpers’ githubClient/slackClient supports idempotency keys or request deduplication for comment/post (write) calls. In the searches performed, nothing in the relay-helpers GitHub code references idempotency-key headers, idempotency-key parameters, or any dedupe mechanism for comment/post operations; results only surfaced unrelated “relay” projects and general idempotency-key articles, not the @relayfile/relay-helpers implementation itself [1][2][3]. Therefore, based on the sources retrieved, the correct conclusion is that idempotency/dedupe is not provided (or at least not discoverable/documented) in githubClient/slackClient for comment/post calls. If you share the relay-helpers repo link (or the specific file paths for githubClient/slackClient), I can re-run a more targeted source-code search for “idempotency”, “dedupe”, “Idempotency-Key”, or “X-Request-ID” within those exact implementations.

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Repo file tree (focus) =="
ls -R

echo "== Find references to relay-helpers / githubClient / slackClient =="
rg -n "relay-helpers|githubClient\(|slackClient\(|failReviewRun|post\(|comment\(" -S .

echo "== Show review/agent.ts around referenced lines =="
sed -n '70,180p' review/agent.ts

echo "== Locate package.json/lock to identify where `@relayfile/relay-helpers` comes from =="
ls
fd -a "package.json" .
fd -a "package-lock.json" .
fd -a "pnpm-lock.yaml" .
fd -a "yarn.lock" .

Repository: AgentWorkforce/agents

Length of output: 8940


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check if node_modules exists (so we can inspect actual relay-helpers code)
if [ -d "node_modules/@relayfile/relay-helpers" ]; then
  echo "Found local node_modules/@relayfile/relay-helpers"
  ls -R node_modules/@relayfile/relay-helpers | head -n 200
  echo "== Search for idempotency/dedupe markers in relay-helpers =="
  rg -n "Idempotency|idempotency|dedupe|dedup|X-Request-ID|requestId|retry|deduplication" node_modules/@relayfile/relay-helpers -S || true
  echo "== Search for GitHub comment/post call wrappers =="
  rg -n "comment\\(|slackClient\\(\\)|post\\(|mergePullRequest\\(" node_modules/@relayfile/relay-helpers -S || true
else
  echo "node_modules/@relayfile/relay-helpers not present in this checkout."
fi

echo "== Search for retry/at-least-once hints in review harness entrypoints =="
rg -n "retry|retries|redeliver|at-?least-?once|idempotent|dedup" review/agent.ts . -S || true

echo "== Show any local docs about idempotency for relay-helpers in repo =="
rg -n "`@relayfile/relay-helpers`|relay-helpers" -S README.md review/README.md . || true

Repository: AgentWorkforce/agents

Length of output: 1197


🌐 Web query:

@relayfile/relay-helpers githubClient slackClient source githubClient().comment slackClient().post idempotency dedupe

💡 Result:

I couldn’t find any public, authoritative evidence for the specific internal chain you referenced: githubClient().comment slackClient().post idempotency dedupe in @relayfile/relay-helpers. What I did find from the public Relayfile ecosystem sources is: 1) Relayfile has a JavaScript SDK and a client (RelayFileClient) and the SDK “adds X-Correlation-Id automatically for API calls” (useful for tracing/replay debugging, but this is not the same as idempotent write deduplication). [1] 2) Relayfile adapters exist for GitHub and Slack (so there are likely code paths where GitHub comments and Slack posts happen), but the sources surfaced in search results didn’t include the @relayfile/relay-helpers implementation details about how dedupe/idempotency is handled for GitHub comment creation or Slack message posting. [2] 3) Some other open-source codebases that integrate with Slack/GitHub implement message/event deduplication explicitly (TTL caches keyed by message/event id), showing the common approach when providers/clients can retry deliveries (e.g., a TTL-based deduplicator). This indicates the pattern you probably want, but it is not proof of Relayfile’s behavior. [3] If you can share the exact GitHub URL (or paste the relevant code) for @relayfile/relay-helpers and where githubClient() / slackClient() are defined, I can re-research specifically for how dedupe keys are generated and where they are applied (e.g., per-post hash, per-operation idempotency keys, or provider-side headers).

Citations:


🌐 Web query:

site:github.com "@relayfile/relay-helpers" githubClient comment implementation

💡 Result:

I couldn’t find any authoritative public source that shows the actual “@relayfile/relay-helpers githubClient comment” implementation (i.e., the code for how @relayfile/relay-helpers uses a GitHub client to create/update PR comments). What I was able to verify from public GitHub sources is that the relayfile ecosystem uses adapters/providers to write back to GitHub comments via an adapter path like /github/repos///pulls/<pull_number>/comments, and that the adapter consumes a provider for auth. Example usage pattern from the relayfile providers repo: - It constructs a RelayFileClient with a Relayfile token. - It uses a GitHubAdapter from @relayfile/adapter-github. - It calls adapter.writeback(...) with a path pointing to a PR comments endpoint and a payload like { body: "Looks good!" }. This demonstrates where a “comment implementation” would be invoked, but not the internal code inside @relayfile/relay-helpers itself. [1] If you can share the specific function/class name inside @relayfile/relay-helpers (or a link to the repository/package source), I can re-run targeted searches for that exact file and produce a fully code-grounded answer for the githubClient comment implementation.

Citations:


🌐 Web query:

site:npmjs.com "@relayfile/relay-helpers" idempotency slackClient

💡 Result:

I couldn’t find any authoritative or directly relevant information about the package name “@relayfile/relay-helpers” (including any mention of “idempotency” or “slackClient”). The web results I found around “relay-helpers” appear to be unrelated packages (e.g., Relay GraphQL helpers/network-layer, or other “relay” libraries), not “@relayfile/relay-helpers”. So, I can’t confidently answer how (or whether) “@relayfile/relay-helpers” implements idempotency for a “slackClient” from evidence. If you paste the repository link (GitHub URL) or the relevant file path(s) (e.g., where slackClient/idempotency are implemented), I can re-run the research specifically on that code/docs and give a grounded answer.

Citations:


Make GitHub/Slack write operations idempotent under handler retries.

review/agent.ts performs unconditional direct write calls; if the platform redelivers/retries the event, these calls will be re-issued, with no dedupe/idempotency guard at this layer.

  • Success path: githubClient().comment(...) (lines 98-99) and slackClient().post(...) (lines 104-109).
  • Failure path: failReviewRun() always re-posts both the GitHub comment and Slack warning (lines 125-132) before throwing.
  • Merge path: mergePr() always posts the Slack “Merged PR” message (line 151).

No accessible evidence shows @relayfile/relay-helpers githubClient() / slackClient() adding Idempotency-Key/request deduplication for comment/post operations.

await githubClient().comment({ owner: pr.owner, repo: pr.repo, number: pr.number }, body);

Require idempotent behavior (e.g., provider/client-side idempotency keys + retry-safe semantics, or local/per-PR-per-run dedupe) or ensure these handler paths cannot be retried/redelivered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@review/agent.ts` around lines 98 - 99, Ensure GitHub and Slack writes are
idempotent by adding a dedupe guard before calling githubClient().comment(...)
and slackClient().post(...): generate a stable idempotency key per PR/run (e.g.,
using pr.owner, pr.repo, pr.number and the review run id) and either pass it to
the client if it supports idempotency headers or check-and-skip by querying
existing PR comments / Slack messages for that key; apply the same guard in
failReviewRun() and mergePr() so they first verify whether the comment/post with
that idempotency key already exists before posting to avoid duplicate writes on
retries.

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 10 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="review/agent.ts">

<violation number="1" location="review/agent.ts:98">
P2: Guard this outbound comment write with idempotency/deduplication. On handler redelivery, this path can post duplicate PR comments.</violation>

<violation number="2" location="review/agent.ts:146">
P1: Merge confirmation uses a truthiness check, which can falsely accept non-boolean values like `'false'` as a successful merge.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread review/agent.ts
if (merged !== true && merged !== 'true') {
// mergePullRequest surfaces the writeback worker's merge outcome as `merged`.
// A false/unconfirmed result means we shouldn't pretend the merge landed.
if (!result.merged) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Merge confirmation uses a truthiness check, which can falsely accept non-boolean values like 'false' as a successful merge.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At review/agent.ts, line 146:

<comment>Merge confirmation uses a truthiness check, which can falsely accept non-boolean values like `'false'` as a successful merge.</comment>

<file context>
@@ -140,36 +122,33 @@ async function failReviewRun(ctx: WorkforceCtx, pr: Pr, reason: string): Promise
-  if (merged !== true && merged !== 'true') {
+  // mergePullRequest surfaces the writeback worker's merge outcome as `merged`.
+  // A false/unconfirmed result means we shouldn't pretend the merge landed.
+  if (!result.merged) {
     throw new Error(`GitHub did not confirm PR #${pr.number} in ${pr.owner}/${pr.repo} was merged.`);
   }
</file context>

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.

Declining: result.merged here is the normalized boolean returned by githubClient().mergePullRequest(...) (Promise<{ merged: boolean; sha?: string }>), not the raw receipt. The client already collapses receipt.merged (which may be true/'true'/false/'false'/undefined-with-sha) into a real boolean'false'false — so if (!result.merged) correctly rejects a failed merge. The string case the old code guarded (merged !== true && merged !== 'true') is handled inside the client now.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for the feedback! I've saved this as a new learning to improve future reviews.

Comment thread review/agent.ts
const client = vfsClient();
if (body) {
await writeJsonFile(client, 'github', 'comment', prCommentPath(pr), { body });
await githubClient().comment({ owner: pr.owner, repo: pr.repo, number: pr.number }, body);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Guard this outbound comment write with idempotency/deduplication. On handler redelivery, this path can post duplicate PR comments.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At review/agent.ts, line 98:

<comment>Guard this outbound comment write with idempotency/deduplication. On handler redelivery, this path can post duplicate PR comments.</comment>

<file context>
@@ -112,19 +94,19 @@ async function reviewAndFix(ctx: WorkforceCtx, pr: Pr): Promise<void> {
-  const client = vfsClient();
   if (body) {
-    await writeJsonFile(client, 'github', 'comment', prCommentPath(pr), { body });
+    await githubClient().comment({ owner: pr.owner, repo: pr.repo, number: pr.number }, body);
   }
 
</file context>

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.

Acknowledged, but deferring out of this PR: this is a mechanical migration (same writes, just via relay-helpers) — the duplicate-on-redelivery behavior is pre-existing, not introduced here. Also the runtime dedupes redeliveries on event.id (WorkforceEventBase.id — "the runtime dedupes on this"), so the handler shouldn't re-run for the same event. Full write-idempotency (stable keys / querying existing comments) is a heavy lift and relay-helpers/the writeback worker don't expose idempotency headers today — better tracked as its own issue than bundled into the migration.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for the feedback.

@khaliqgant khaliqgant merged commit b6e4e90 into main Jun 2, 2026
2 checks passed
@khaliqgant khaliqgant deleted the feat/migrate-to-relay-helpers branch June 2, 2026 11:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant