Skip to content

fix(verify): stop feature verification reporting false green - #1392

Merged
khaliqgant merged 7 commits into
mainfrom
feat/verify-features-hardening
Jul 30, 2026
Merged

fix(verify): stop feature verification reporting false green#1392
khaliqgant merged 7 commits into
mainfrom
feat/verify-features-hardening

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jul 30, 2026

Copy link
Copy Markdown
Member

Why

A verify-features run reported COMPLETED — 11 passed, 0 failed while Tier 2 had four failing checks. This makes that impossible, then makes a failure actionable.

The false green had three causes, all fixed

  1. Every step set failOnError: false, so a non-zero exit scored as a pass. Tiers still use it (all tiers should run even after one fails), but they are no longer the verdict — each check appends pass/fail/skip to checks.jsonl and a verdict step aggregates it.
  2. Nothing aggregated tier results. verdict + a terminal enforce-verdict now do, and main() exits non-zero.
  3. main() discarded the runner result. It now reads verdict.json and fails closed when that file is missing.

A fourth defect nobody asked about

applyReliabilityDefaults in @relayflows/core force-enables strategy: "retry" with a repair agent for any workflow declaring agents. On the pasted run, tier1-cli-health failed, the reporter agent was handed the gate, edited two files, and it passed. A verification workflow whose assertions an agent can rewrite until they pass measures nothing. Fixed with onError('continue'), the documented opt-out, confirmed via the built config (errorHandling: {"strategy":"continue"}).

The run also verified the wrong CLI

The sandbox ran agent-relay 10.0.0 against an 11.3.0 checkout — it logged Update available: 10.0.0 → 11.3.0, then reported relay node deadletters as unknown. That is a real command in 11.x (registerCoreCommands(node, …), packages/cli/src/cli/commands/core.ts:394). A whole run measured a stale published CLI and produced a red herring. A provenance step now fails on CLI/repo version drift.

Honest accounting

cloud whoami / cloud session were scored as product failures when the sandbox simply wasn't cloud-authed. A capabilities probe now turns unrunnable checks into SKIPs with a stated cause — never a pass, never a fail. verdict also fails the run when a tier produced zero records, so a tier that crashes before its first check cannot masquerade as clean.

What happens on failure now

Slack alert → GitHub issue → fix attempt on a branch → draft PR with re-verified evidence. An integrity gate refuses a branch that reduced verification call sites, left the repo on main, or has no root cause on record — a fixer's cheapest path to green is always to weaken the assertion.

Harness-level breakage (no verdict, tier not run, broker never started, no provider CLI) escalates to NightCTO via the CloudEvidenceSummary v1 contract. Run and per-failure events go to PostHog, posted directly rather than through the CLI's telemetry client — that one is opt-in product telemetry keyed to a real user.

Slack goes through @relayflows/slack-primitive pinned to its cloud-relay runtime (/api/v1/slack/post-message, the workspace's Slack integration). Pinning matters: auto-detection falls back to a local SLACK_BOT_TOKEN runtime, so a stray token silently changed the route. This also fixes a wrong endpoint — the previous fallback used /api/v1/integrations/slack/messages, which does not exist.

Coverage

Tiers 1–6 and all six critical paths, including a real MCP JSON-RPC handshake asserting the required tool set (previously just relay mcp --help).

Is the feature map current? No — and the old check couldn't tell

manifest-contract.test.ts asserts a hardcoded list of ~110 commands, so a newly added command is absent from both the manifest and the expectation list and passes silently. scripts/audit-feature-manifest.mjs derives the surface instead (recursive --help + tools/list).

Its first run found fleet spawn and fleet release shipping undocumented, therefore unverified. Both added; audit reports MANIFEST_CLEAN. workflows/audit-feature-manifest.ts runs this on a schedule with the same Slack/issue/PR/NightCTO/PostHog treatment. Exit 2 (audit broke) is deliberately distinct from exit 0 — a broken audit must never read as a clean manifest.

Verification

Ran and confirmed:

  • Typecheck clean; both dry runs Validation: PASS.
  • All 30 generated shell bodies pass sh -n.
  • Verdict gate against all four failure modes: all-pass→PASS, failing check→FAIL, crashed tier→FAIL, missing ledger→FAIL. enforce-verdict exits 0/1/2 correctly.
  • Check harness reports 2p/2f/2s exactly matching the ledger, including direct record calls (a bug I found and fixed mid-review: the counters lived in the callers, so multi-step checks logged 0 passed, 0 failed while the ledger held real results).
  • Ledger survives quotes, newlines, tabs, backslashes; bounds 2000-char output to 400.
  • Slack proven against a stub server: correct path, bearer auth, channel C0AEKNLDNKW. With SLACK_BOT_TOKEN set and CLOUD_API_* absent it fails loudly instead of falling back.
  • Drift detection proven by temporarily deleting the entry I added.
  • 32 feature-guardian tests pass; prettier clean.

Not verified: no live end-to-end run — the tiers need a broker, workspace, cloud auth, and provider CLIs. The gh issue create / gh pr create paths were deliberately not executed, since that would file real issues.

Deliberate omissions

  • No CHANGELOG entry. The changelog is the user-facing release narrative; this is internal verification tooling, like the explicitly-excluded relay-feature-guardian. (There is contrary precedent at line 168.)
  • Tier 5/6 checks that spend real money or need a browser (cloud run, fleet spawn, OAuth logins) are recorded as explicit SKIPs with reasons rather than attempted, so the coverage hole is visible instead of hidden.

🤖 Generated with Claude Code

Review in cubic


Live end-to-end run (replaces the earlier "not verified" caveat)

Ran the whole pipeline for real against the 11.3.0 build with a live broker, an authenticated cloud session, and all four provider CLIs present. Isolated working directory; the repo checkout was never touched.

Result: 90 passed, 3 failed, 17 skipped → verdict FAIL, enforce-verdict exit 1.

Tier Result p/f/s
provenance PASS 2/0/0
tier1 CLI health PASS 26/0/0
tier2 broker PASS 8/0/0
tier3 messaging PASS 17/0/0
tier4 cross-agent FAIL 8/1/0
tier5 cloud FAIL 12/2/13
tier6 harnesses PASS 11/0/4
critical paths 1–6 PASS 6/0/0

Tier 6 and CP3 spawned, held, resumed, and released a real Claude agent. CP4 completed a real MCP JSON-RPC handshake. CP5 ran a real local workflow and asserted the sentinel plus a completed sync.

relay node deadletters passes against 11.3.0, confirming the original sandbox failure was purely the stale 10.0.0 CLI — exactly what the new provenance step now catches.

The run found 3 real product bugs

Each reproduced independently, outside the harness, with clean fixtures. Left failing rather than worked around.

  1. relay message file upload cannot succeed. Three distinct errors as the invocation was corrected, ending at server-side Invalid attachments: file ids must exist in workspace and be complete. Also, --help documents --text as defaulting to "", but the API rejects empty text with text is required — the documented default cannot work.
  2. relay integration webhook create <url> returns channel is required, but --help exposes no --channel option. The command as documented cannot succeed.
  3. relay agent remove fails for any agent that has sent a message, leaking a raw SQL error: Failed query: delete from "agents" where "agents"."id" = ?. Exit code is correctly 1, but the message is an internal query dump. This silently accumulated 16 test identities in the workspace during these runs, while every tier still reported clean — so tier 3 now asserts the agent is actually gone after removal instead of assuming it.

relay fleet config also fails with RelaycastMessagingClient.workspace.fleetNodes requires @relaycast/sdk with the workspace fleet nodes API, which looks like a dependency/capability gap rather than a code defect.

And 8 bugs in this workflow, now fixed

  • grep -q under set -o pipefail-q exits at the first match, the upstream writer takes SIGPIPE, and the pipeline reports failure. A demonstrably RUNNING broker probed as absent. Fixed at 10 piped sites including run_check, where it was a latent false-FAIL generator for any long-output check.
  • capabilities died mid-probe — it read $CLOUD_API_URL without seeding it, so set -u killed the step and every provider_* flag went unwritten, silently degrading tier 5/6 gating.
  • Token extraction matched any 20+ char string — a failed registration yielded the agent's own name as its token, so downstream calls failed for unrelated reasons. Now parses the JSON token field (7 sites).
  • VERIFY_AUTOFIX=0 did not stop the fixer — it gated file-issue but not attempt-fix, so the documented off switch still let an agent edit the tree.
  • Three checks contradicted the manifest, which had the correct signatures all along: message file upload <path> --channel, cloud status <runId>, integration webhook create <url>. Good evidence the manifest is worth keeping accurate.
  • CP5 asserted against the launch responseworkflow run is async and returns status: "running", so the sentinel could never appear there. It now polls the log as critical-path 5 documents.

Still not exercised

Slack delivery was verified against a stub server (correct path, bearer auth, channel C0AEKNLDNKW) rather than live — this machine has a cloud session but no CLOUD_API_URL/CLOUD_API_TOKEN in the environment, so the slack capability probes 0. The gh issue create / gh pr create paths were deliberately not executed, since that would file real issues.

Note: these runs left ~16 test agents in the Default workspace that cannot be deleted because of product bug 3 above.


Bugs fixed (follow-up to the live run)

integration webhook create — fixed here

POST /v1/webhooks accepts { channel, name? } and returns the url. The command took a <url> argument and sent { url, event }, so the required channel was never sent and every call failed with channel is required. url is not a request field at all; event belongs to subscriptions.

  • Command now takes <channel> with an optional --name, matching create-inbound — which posts to the same endpoint with the same schema (relay.js:428-429), making them true aliases.
  • RelayCreateWebhookInput corrected from { url, event? } to { channel, name? }. Anyone passing url/event was already failing at runtime, so this breaks no working code.
  • Regression test pins the corrected contract, including that url/event are never sent.
  • Verified against the live API: create returned a real webhookId/url/token, and the workflow's create → trigger → delete lifecycle now passes. Tier 5 went from 12p/2f to 15p/1f.

The audit had been reading a stale build

packages/cli/dist was older than packages/cli/src, so the audit derived a surface missing 16 shipped commands and printed MANIFEST_CLEAN across several runs. Same class of error as the sandbox verifying CLI 10.0.0 against an 11.3.0 checkout — and the reason it went unnoticed.

Now documented: agent me, agent presence, the cloud integration tree (catalog, connect, connections, disconnect) and the cloud room tree (session, revoke-session, members, remove-member, invite, invites, accept, revoke-invite). Two new manifest categories, each mapped to a verification procedure. 180 → 194 features.

The audit now warns when dist is older than src, so a stale build cannot yield a falsely clean result again.

Filed, not fixable in this repo

All three reproduced independently with clean fixtures. Their checks are left failing rather than skipped, so the run stays red until they are fixed.

Proactive Runtime Bot and others added 2 commits July 30, 2026 14:06
A verify-features run with four failing Tier 2 checks reported "COMPLETED —
11 passed, 0 failed". Four independent defects produced that, and each is
closed here.

1. Every step set failOnError: false, so a non-zero exit scored as a pass.
   Tiers still use it (all tiers should run even after one fails), but they
   are no longer the verdict: each check now appends pass/fail/skip to
   checks.jsonl and a `verdict` step aggregates it into verdict.json.
2. Nothing aggregated tier results into a run verdict. `verdict` plus a
   terminal `enforce-verdict` now do, and main() exits non-zero.
3. main() discarded the runner result. It now reads verdict.json and fails
   closed when that file is missing.
4. applyReliabilityDefaults force-enables strategy "retry" with a repair
   agent for any workflow declaring agents, so a failing verification gate
   was handed to an agent that edited the tree until it passed. onError is
   now "continue", which opts out of that path.

The run also verified agent-relay 10.0.0 against an 11.3.0 checkout — it
logged "Update available: 10.0.0 -> 11.3.0" and then reported `relay node
deadletters` as unknown, a real command in 11.x. A `provenance` step now
fails on CLI/repo version drift. A `capabilities` probe turns unrunnable
checks (no cloud login, no provider CLI) into SKIPs with a stated cause
rather than product failures, and `verdict` fails the run when a tier
produced no records at all, so a crashed tier cannot look clean.

Coverage now spans tiers 1-6 and all six critical paths, including a real
MCP JSON-RPC handshake asserting the required tool set (previously just
`relay mcp --help`).

On failure the run posts to Slack, files a GitHub issue, attempts a fix on a
branch, and opens a draft PR. An integrity gate refuses a branch that
reduced the number of verification call sites, left the repo on main, or has
no root cause on record. Harness-level breakage escalates to NightCTO via
the CloudEvidenceSummary v1 contract; run and per-failure events go to
PostHog.

Adds scripts/audit-feature-manifest.mjs and workflows/audit-feature-manifest.ts
to answer whether the feature map is current. manifest-contract.test.ts
asserts a hand-maintained list, so it cannot notice a newly added command;
the audit derives the surface from recursive `--help` and `tools/list`
instead. Its first run found `fleet spawn` and `fleet release` shipping
undocumented, and therefore unverified — both are now in the manifest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both workflows now post through @relayflows/slack-primitive pinned to its
cloud-relay runtime, which proxies /api/v1/slack/post-message and delivers
via the workspace's configured Slack integration (the Nango connection).

Pinning the runtime is load-bearing. selectRuntime() prefers cloud-relay but
falls back to a local SLACK_BOT_TOKEN runtime that talks to slack.com
directly, so leaving it on auto meant a stray bot token in the environment
silently changed the delivery route. Pinned, a missing CLOUD_API_* pair
raises auth_token_missing instead. Verified: with SLACK_BOT_TOKEN set and
CLOUD_API_* absent, delivery fails loudly rather than falling back.

Also fixes the endpoint. The previous cloud fallback POSTed to
/api/v1/integrations/slack/messages, which does not exist; the primitive's
actual path is /api/v1/slack/post-message. Confirmed against a stub server:
POST /api/v1/slack/post-message, bearer CLOUD_API_TOKEN, body
{channel, text, unfurlLinks, unfurlMedia}.

Default channel is now the ID C0AEKNLDNKW rather than a #name. The
cloud-relay runtime does not implement channel resolution, so an ID is the
only form guaranteed to resolve. Override with VERIFY_SLACK_CHANNEL /
AUDIT_SLACK_CHANNEL.

An undelivered alert still echoes its full payload to the run log, so a
delivery failure can never read as a delivered alert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@khaliqgant
khaliqgant requested a review from willwashburn as a code owner July 30, 2026 13:08
@cursor

cursor Bot commented Jul 30, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 785f03ab-0196-4d67-a0a9-130acbdd4507

📥 Commits

Reviewing files that changed from the base of the PR and between 1062989 and 724ff58.

📒 Files selected for processing (2)
  • .agentworkforce/features/verify/procedures.md
  • packages/cli/src/cli/commands/relaycast-groups.test.ts

📝 Walkthrough

Walkthrough

The PR changes webhook creation to use channel and optional name, expands manifest coverage, improves manifest auditing and remediation controls, and strengthens verification provenance, cleanup, ledger parsing, and verdict generation.

Changes

Feature Verification

Layer / File(s) Summary
Webhook request contract
packages/cli/src/cli/commands/integration.ts, packages/sdk/src/messaging/types.ts, packages/.../integration*, .agentworkforce/features/verify/procedures.md, CHANGELOG.md
Webhook creation now uses channel and optional name, with aligned SDK types, tests, procedures, manifest documentation, and changelog entries.
Manifest and command coverage
.agentworkforce/features/manifest.yaml, .agentworkforce/agents/.../manifest-contract.test.ts, .claude/skills/verify-features.md
The manifest adds agent, cloud, and fleet features; command coverage and audit guidance are updated.
Runtime manifest audit
scripts/audit-feature-manifest.mjs
The audit derives CLI and MCP surfaces, handles positional commands and MCP failures, and distinguishes drift from audit failures.
Scheduled audit and remediation
.gitignore, workflows/audit-feature-manifest.ts
The workflow persists autofix state, guards malformed reports, gates remediation and integrity checks, and restricts fix-branch edits to the manifest.
Verification checks and verdict integrity
workflows/verify-features.ts
CLI provenance, command outcomes, cleanup scoping, ledger sanitization, explicit skips, and malformed-line verdict handling are strengthened.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AuditWorkflow
  participant AuditScript
  participant Manifest
  participant ManifestEditor
  participant VerifyWorkflow
  AuditWorkflow->>AuditScript: Derive CLI and MCP surfaces
  AuditScript->>Manifest: Compare live and documented entries
  AuditScript-->>AuditWorkflow: Return clean, drift, or audit failure
  AuditWorkflow->>ManifestEditor: Apply autofix when enabled
  ManifestEditor-->>AuditWorkflow: Write manifest update summary
  VerifyWorkflow->>VerifyWorkflow: Run checks and generate verdict
Loading

Possibly related PRs

Suggested reviewers: willwashburn

Poem

I’m a rabbit auditing trails,
Fleet commands hop past the rails.
Webhooks carry channels bright,
Verdicts sort the wrong from right,
Clean manifests twitch their tails.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: preventing false-green verify-features runs.
Description check ✅ Passed The description is thorough and covers the change and verification, though it doesn't follow the template headings exactly.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
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.
✨ 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/verify-features-hardening

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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 72539f7b88

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +500 to +502
elif [ "$CLI_VERSION" = "$REPO_VERSION" ]; then
echo " PASS cli-matches-repo ($CLI_VERSION)" | tee -a "$LOG"
record "$TIER" "cli-matches-repo" pass ""

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 Badge Verify the checkout rather than matching only its version

When the globally installed relay and the checkout both report the same package version—as normally happens for feature branches between releases—this records a pass even though every subsequent check still invokes the global binary and may exercise none of the branch's changes. The provenance gate should require the CLI path/build to belong to this checkout, or invoke the checkout's built entry point directly, rather than treating version equality as proof of provenance.

Useful? React with 👍 / 👎.

Comment thread workflows/verify-features.ts Outdated
Comment on lines +810 to +811
run_check "message file upload" \
"RELAY_AGENT_TOKEN='$TOKEN_A' relay message file upload '$CHANNEL' '$UPLOAD_FILE'" "."

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 Badge Pass the file path and channel flag to upload

Whenever Tier 4 reaches this check, it invokes the command with the channel as the positional path and the fixture as an unexpected second positional argument, so a working upload implementation is reported as failing. The command definition at packages/cli/src/cli/commands/message.ts:240-245 requires upload <path> --channel <channel>; this check needs to pass $UPLOAD_FILE first and $CHANNEL through --channel.

Useful? React with 👍 / 👎.

Comment thread workflows/verify-features.ts Outdated
Comment on lines +1714 to +1718
- If the file does not exist, or its "verdict" field is "PASS", there is NOTHING
to fix. Write "No failures — nothing to fix." to
${ARTIFACTS}/fix-summary.md and STOP IMMEDIATELY. Do not read other files, do
not create a branch, do not edit anything, do not run any commands.
- Only if "verdict" is "FAIL" do you continue with the rest of this task.

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 Badge Honor VERIFY_AUTOFIX before launching the fixer

When VERIFY_AUTOFIX=0 and the verdict is FAIL, only file-issue observes the flag; this agent task checks only the verdict, so it still creates a branch, edits and commits code, and the downstream integrity and open-pr steps can still push and open a draft PR. Include the autofix setting in this early-stop contract (and gate the downstream path) so the documented opt-out actually disables the fix/PR workflow.

Useful? React with 👍 / 👎.

Comment thread workflows/audit-feature-manifest.ts Outdated
Comment on lines +514 to +517
- If it does not contain exactly \`1\`, there is NOTHING to do. Write "No drift —
nothing to update." to ${ARTIFACTS}/update-summary.md and STOP IMMEDIATELY:
do not create a branch, edit files, or run commands.
- Only if it contains \`1\` do you continue.

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 Badge Honor AUDIT_AUTOFIX before launching the editor

When AUDIT_AUTOFIX=0 and drift is found, file-issue exits successfully without filing an issue, but this task proceeds solely because audit-exit.txt contains 1; it can therefore create a branch and commit a manifest update, after which open-pr can push it. Make the editor and all downstream mutation steps stop when autofix is disabled, not just when the audit is clean.

Useful? React with 👍 / 👎.

Comment on lines +338 to +342
const drift = undocumentedCommands.length + staleCommands.length + undocumentedMcp.length + staleMcp.length;

const report = {
ok: drift === 0,
auditable: true,

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 Badge Fail when the MCP surface cannot be derived

If relay mcp crashes, times out, or never returns tools/list while the CLI manifest has no other drift, mcpTools is null, both MCP drift arrays become empty, and drift remains zero; the script then reports ok: true, prints MANIFEST_CLEAN, and exits 0 despite explicitly saying MCP drift was not checked. Treat an underivable MCP surface as audit failure/exit 2 so a broken half of the audit cannot produce a clean result.

Useful? React with 👍 / 👎.

Comment thread scripts/audit-feature-manifest.mjs Outdated
Comment on lines +317 to +321
const staleCommands = [...documentedCommands].filter((cmd) => {
const parts = cmd.split(' ');
for (let i = parts.length; i >= 1; i--) {
if (cliLeafSet.has(parts.slice(0, i).join(' '))) return false;
}

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 Badge Require the full CLI leaf when detecting stale entries

When a documented subcommand is removed but its parent group remains—for example, fleet spawn disappears while fleet still exists—this loop finds the parent prefix in cliLeafSet and declares the manifest entry non-stale. Because deriveCliLeaves() deliberately adds every parent group, almost any removed child command is hidden; positional-dispatch exceptions need to be identified explicitly rather than allowing any surviving prefix to validate an arbitrary documented suffix.

Useful? React with 👍 / 👎.

Comment thread workflows/verify-features.ts Outdated
Comment on lines +848 to +850
gated_check cloud "cloud status" "relay cloud status" "."
gated_check cloud "cloud schedules" "relay cloud schedules" "."
gated_check cloud "cloud logs" "relay cloud logs 2>&1 || true" "."

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 Badge Supply run IDs to cloud status and logs

In a cloud-authenticated environment, both checks omit the required <runId> defined at packages/cli/src/cli/commands/cloud.ts:897-925: cloud status therefore always records a failure, while cloud logs masks the parser failure with || true and records a false pass because the error text matches .. Use a real fixture run ID, or record these as explicit skips when no run exists.

Useful? React with 👍 / 👎.

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

Actionable comments posted: 9

🧹 Nitpick comments (3)
workflows/audit-feature-manifest.ts (2)

490-491: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Scheduled runs will file a duplicate issue every week while drift persists.

Nothing checks for an already-open drift issue, and the title is run-ID-unique so GitHub won't dedupe either. Consider searching for an open issue with a stable label/marker first and commenting on it instead of creating a new one.

♻️ Sketch
+EXISTING=$(gh issue list --state open --label manifest-drift --limit 1 --json url --jq '.[0].url' 2>/dev/null || true)
+if [ -n "$EXISTING" ]; then
+  gh issue comment "$EXISTING" --body-file "$ARTIFACTS/issue-body.md" >/dev/null 2>&1 || true
+  echo "$EXISTING" > "$ARTIFACTS/issue-url.txt"
+  echo "ISSUE_UPDATED: $EXISTING"
+  exit 0
+fi
+
 ISSUE_URL=$(gh issue create --title "Feature manifest drift: ${RUN_ID}" \
-  --body-file "$ARTIFACTS/issue-body.md" 2>&1 | grep -oE 'https://[^ ]+' | head -1 || true)
+  --label manifest-drift --body-file "$ARTIFACTS/issue-body.md" 2>&1 | grep -oE 'https://[^ ]+' | head -1 || true)
🤖 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 `@workflows/audit-feature-manifest.ts` around lines 490 - 491, Update the
issue-reporting flow around ISSUE_URL to search for an existing open drift issue
using a stable label or marker before invoking gh issue create. If one exists,
reuse its URL and add the current drift details as a comment; only create a new
issue when none is found, and ensure newly created issues receive the stable
identifier.

185-190: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Two full audit invocations double the CLI walk.

Each invocation spawns --help for every node of the command tree plus an MCP handshake, so this pays that cost twice against a 900s workflow timeout — and the two runs can disagree (the second's exit code is discarded via || true, so audit.json may not describe the run whose exit code drives everything downstream). Consider running once with --json and rendering the human-readable log from the JSON, or having the script emit both in one pass.

🤖 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 `@workflows/audit-feature-manifest.ts` around lines 185 - 190, Update the audit
workflow to invoke scripts/audit-feature-manifest.mjs only once, preserving a
single authoritative exit code and report. Have that invocation produce both
machine-readable JSON for audit.json and a human-readable audit.txt, either by
using the script’s combined-output support or by rendering the human-readable
log from the captured JSON; remove the second invocation and discarded exit-code
path.
workflows/verify-features.ts (1)

1128-1135: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

TOOL_COUNT counts quote characters and is never used.

grep -o '"' | wc -l is not a tool count and nothing reads the variable. Drop it, or derive a real count from the parsed tools array in cp4-mcp.mjs and record it in the reason.

♻️ Proposed cleanup
 if node "${ARTIFACTS}/cp4-mcp.mjs" > "$MCP_OUT" 2>/dev/null && grep -q '"ok":true' "$MCP_OUT"; then
-  TOOL_COUNT=$(grep -o '"' "$MCP_OUT" | wc -l | tr -d ' ')
   # Assert the tools we depend on are actually listed, not just that some
🤖 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 `@workflows/verify-features.ts` around lines 1128 - 1135, Remove the unused
TOOL_COUNT assignment from the MCP verification block, including its
quote-counting pipeline; keep the existing required-tool checks in the loop
unchanged.
🤖 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 `@scripts/audit-feature-manifest.mjs`:
- Line 325: Update the audit result and exit-status logic around
undocumentedMcp, staleMcp, and the final manifest status so a null result from
deriveMcpTools() is treated as unauditable rather than clean. Ensure the script
exits with the existing non-clean audit status (such as 2) when mcpDerivable is
false, while preserving normal drift detection for successfully derived MCP
data; only allow a clean exit when the MCP surface was derivable or an explicit
opt-out flag is supported.
- Around line 192-204: Guard the child.stdin writes in the send helper used by
the initialize and tools/list requests against broken or destroyed streams.
Attach an error handler or otherwise safely suppress EPIPE and
ERR_STREAM_DESTROYED, ensuring stdin failures are handled through the existing
audit failure path and preserve the intended exit status 2 rather than causing
an uncaught exception.

In `@workflows/audit-feature-manifest.ts`:
- Around line 346-397: Guard JSON.parse in both the slack-alert builder at
workflows/audit-feature-manifest.ts:346-397 and the file-issue builder at
workflows/audit-feature-manifest.ts:430-433. When audit.json is missing, empty,
truncated, or invalid, preserve the notification flow by emitting a minimal
drift-detected message for Slack and a minimal issue body containing the exit
code and artifact link, instead of allowing the Node scripts to fail or produce
empty output.
- Around line 638-651: Strengthen the integrity gate in the workflow block
around the existing scripts/audit-feature-manifest.mjs check to validate the
measured surface as well as the audit script. Detect changes to the CLI command
definitions and manifest-contract.test.ts expectation list, mark INTEGRITY=fail,
and exit before accepting MANIFEST_CLEAN when either measuring surface is
modified.

In `@workflows/verify-features.ts`:
- Around line 161-170: Update record in workflows/verify-features.ts:161-170 to
remove all ASCII control characters, including DEL, from the reason before
escaping and truncating it. Update the verdict-reading logic in
workflows/verify-features.ts:1264-1298 to count JSON.parse failures and add a
reason such as “N unparseable ledger line(s)” so malformed ledger entries affect
the run verdict rather than being silently discarded.
- Around line 637-645: Update the "node tail (bounded)" check so only exit
status 0 or timeout status 124 is accepted; reject other nonzero statuses such
as command or broker failures while preserving the existing timeout-success
behavior.
- Around line 1012-1019: Update the CP1 lifecycle assertion using the relay node
status command so it verifies that the complete status output does not contain
“running,” rather than succeeding when any individual line lacks it. Replace the
grep-based inverted match with a negated match while preserving the existing CP1
counter and pass/fail handling.
- Around line 1211-1217: Restrict the cleanup loops around the relay agent and
channel listing commands to harness-owned names only: match complete tokens
beginning with the expected vf- or cp[0-9] prefix, rather than matching those
substrings anywhere in output. Prefer including the current ${SUFFIX} in the
pattern so relay agent remove and relay channel archive only affect objects
created by this run.
- Around line 527-568: Initialize the shell command with the shared
${ENV_DEFAULTS} content before the probe function and capability checks execute.
Ensure the defaults are applied before probe slack evaluates CLOUD_API_URL and
CLOUD_API_TOKEN, while preserving the existing provider_ and provider_any probe
behavior.

---

Nitpick comments:
In `@workflows/audit-feature-manifest.ts`:
- Around line 490-491: Update the issue-reporting flow around ISSUE_URL to
search for an existing open drift issue using a stable label or marker before
invoking gh issue create. If one exists, reuse its URL and add the current drift
details as a comment; only create a new issue when none is found, and ensure
newly created issues receive the stable identifier.
- Around line 185-190: Update the audit workflow to invoke
scripts/audit-feature-manifest.mjs only once, preserving a single authoritative
exit code and report. Have that invocation produce both machine-readable JSON
for audit.json and a human-readable audit.txt, either by using the script’s
combined-output support or by rendering the human-readable log from the captured
JSON; remove the second invocation and discarded exit-code path.

In `@workflows/verify-features.ts`:
- Around line 1128-1135: Remove the unused TOOL_COUNT assignment from the MCP
verification block, including its quote-counting pipeline; keep the existing
required-tool checks in the loop unchanged.
🪄 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: 357a2055-1fac-477f-af3a-624864fde413

📥 Commits

Reviewing files that changed from the base of the PR and between f8a150b and 0f010e6.

📒 Files selected for processing (7)
  • .agentworkforce/agents/relay-feature-guardian/manifest-contract.test.ts
  • .agentworkforce/features/manifest.yaml
  • .claude/skills/verify-features.md
  • .gitignore
  • scripts/audit-feature-manifest.mjs
  • workflows/audit-feature-manifest.ts
  • workflows/verify-features.ts

Comment thread scripts/audit-feature-manifest.mjs
Comment thread scripts/audit-feature-manifest.mjs
Comment thread workflows/audit-feature-manifest.ts
Comment thread workflows/audit-feature-manifest.ts Outdated
Comment thread workflows/verify-features.ts
Comment thread workflows/verify-features.ts
Comment on lines +637 to +645
# 'relay node tail' streams until interrupted; bound it and accept a timeout
# kill as success. Without the bound this step hangs until the run times out.
run_check "node tail (bounded)" \
"timeout 5 relay node tail >/dev/null 2>&1; [ \$? -le 124 ] && echo tail-ok" "tail-ok"

echo "" | tee -a "$LOG"
echo "Tier 2 result: $PASS passed, $FAIL failed" | tee -a "$LOG"
# Redeliver against an empty queue is a no-op, so this exercises the command
# path without mutating real delivery state.
run_check "node redeliver --all (empty queue)" \
"relay node redeliver --all 2>&1 || true" "."

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

[ $? -le 124 ] treats real failures as success.

Only 0 (clean exit) and 124 (timeout kill) should count. Exit 1 (unknown command, broker unreachable) is -le 124, so a removed or broken relay node tail still records PASS — the precise class of false green this file targets.

🐛 Proposed fix
 run_check "node tail (bounded)" \
-  "timeout 5 relay node tail >/dev/null 2>&1; [ \$? -le 124 ] && echo tail-ok" "tail-ok"
+  "timeout 5 relay node tail >/dev/null 2>&1; _rc=\$?; { [ \$_rc -eq 0 ] || [ \$_rc -eq 124 ]; } && echo tail-ok" "tail-ok"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 'relay node tail' streams until interrupted; bound it and accept a timeout
# kill as success. Without the bound this step hangs until the run times out.
run_check "node tail (bounded)" \
"timeout 5 relay node tail >/dev/null 2>&1; [ \$? -le 124 ] && echo tail-ok" "tail-ok"
echo "" | tee -a "$LOG"
echo "Tier 2 result: $PASS passed, $FAIL failed" | tee -a "$LOG"
# Redeliver against an empty queue is a no-op, so this exercises the command
# path without mutating real delivery state.
run_check "node redeliver --all (empty queue)" \
"relay node redeliver --all 2>&1 || true" "."
# 'relay node tail' streams until interrupted; bound it and accept a timeout
# kill as success. Without the bound this step hangs until the run times out.
run_check "node tail (bounded)" \
"timeout 5 relay node tail >/dev/null 2>&1; _rc=\$?; { [ \$_rc -eq 0 ] || [ \$_rc -eq 124 ]; } && echo tail-ok" "tail-ok"
# Redeliver against an empty queue is a no-op, so this exercises the command
# path without mutating real delivery state.
run_check "node redeliver --all (empty queue)" \
"relay node redeliver --all 2>&1 || true" "."
🤖 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 `@workflows/verify-features.ts` around lines 637 - 645, Update the "node tail
(bounded)" check so only exit status 0 or timeout status 124 is accepted; reject
other nonzero statuses such as command or broker failures while preserving the
existing timeout-success behavior.

Comment thread workflows/verify-features.ts Outdated
Comment thread workflows/verify-features.ts Outdated
Proactive Runtime Bot and others added 2 commits July 30, 2026 16:08
Ran the pipeline for real against the 11.3.0 build with a live broker, cloud
login, and all four provider CLIs: 90 passed, 3 failed, 17 skipped, exit 1.
Fixes below are the workflow's own bugs that the run exposed.

- grep -q under `set -o pipefail`: -q exits at the first match, the upstream
  writer takes SIGPIPE, and the pipeline reports failure. A demonstrably
  RUNNING broker probed as absent. Fixed at 10 piped call sites, including
  run_check itself, where it was a latent false-FAIL generator for any check
  whose command produced long output.
- The capabilities step read $CLOUD_API_URL without seeding it, so `set -u`
  killed it mid-probe and every provider_* flag went unwritten. Tier 5/6
  gating and the NightCTO provider escalation both read a truncated file.
- Token extraction matched any 20+ char string, so a failed registration
  ("agent already exists") yielded the agent's own NAME as its token and every
  downstream call failed for unrelated reasons. Now parses the JSON token
  field. Fixed at 7 sites.
- VERIFY_AUTOFIX=0 gated file-issue but not attempt-fix, so the documented off
  switch did not stop an agent from editing the tree. The flag is now written
  to autofix.env and honoured by attempt-fix and fix-integrity.
- Three checks contradicted signatures the manifest already documented
  correctly: `message file upload <path> --channel`, `cloud status <runId>`
  (now a stated skip — no disposable run is made), and
  `integration webhook create <url>`.
- CP5 asserted the sentinel against the launch response, but `workflow run` is
  asynchronous and returns status "running". It now polls the log for the
  sentinel and asserts a completed sync, as critical-path 5 documents.
- Added a post-removal assertion in tier 3: `agent remove` reporting success
  was assumed to mean the agent was gone. It is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixes what the live verification run found, and one thing that run could not
see because it was measuring a stale build.

## integration webhook create never worked

`POST /v1/webhooks` accepts `{ channel, name? }` and RETURNS the url. The
command took a `<url>` argument and sent `{ url, event }`, so the required
channel was never sent and every invocation failed with "channel is required".
`url` is not a request field at all, and `event` belongs to subscriptions.

- CLI now takes `<channel>` with an optional `--name`, matching create-inbound,
  which posts to the same endpoint with the same schema.
- `RelayCreateWebhookInput` corrected from `{ url, event? }` to
  `{ channel, name? }`. Callers passing url/event were already failing.
- Verified against the live API: create returns a real webhookId/url/token, and
  the workflow's create -> trigger -> delete lifecycle now passes.

## The audit was reading a stale dist

`packages/cli/dist` was older than `packages/cli/src`, so the audit derived a
surface missing 16 shipped commands and reported MANIFEST_CLEAN across several
runs — the same class of error as the sandbox verifying CLI 10.0.0 against an
11.3.0 checkout, and the reason it went unnoticed.

Now documented, so they are covered rather than invisible: `agent me`,
`agent presence`, the `cloud integration` tree (catalog, connect, connections,
disconnect) and the `cloud room` tree (session, revoke-session, members,
remove-member, invite, invites, accept, revoke-invite). Two new manifest
categories, each mapped to a verification procedure. 180 -> 194 features.

The audit now warns when dist is older than src, so a stale build cannot
produce a falsely clean result again.

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

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

Actionable comments posted: 4

♻️ Duplicate comments (5)
scripts/audit-feature-manifest.mjs (2)

192-204: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

child.stdin still unguarded against EPIPE (unresolved from prior review).

send() writes to child.stdin with no error handler; a broken pipe (spawn failure, or finish()'s own child.kill()) can still surface as an uncaught exception outside main().catch(...), exiting 1 instead of the intended 2, and workflows/audit-feature-manifest.ts would then treat the broken audit as confirmed drift.

🛡️ Proposed fix
+    child.stdin.on('error', () => finish(null));
+
     const send = (msg) => child.stdin.write(`${JSON.stringify(msg)}\n`);
🤖 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 `@scripts/audit-feature-manifest.mjs` around lines 192 - 204, Guard the `send`
helper in the audit request flow against `child.stdin` write failures, including
EPIPE caused by spawn failure or `child.kill()`, so they are captured by the
existing `main().catch(...)` path and produce the intended exit code 2. Attach
appropriate stdin error handling and propagate the failure through `send`
without changing the JSON-RPC messages or audit behavior.

381-381: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

MCP-null still allows MANIFEST_CLEAN/exit 0 (unresolved from prior review).

When deriveMcpTools() resolves null, undocumentedMcp is forced to []; combined with no CLI drift, drift can be 0 and the script exits 0, contradicting the header contract that a broken audit must not read as clean.

🤖 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 `@scripts/audit-feature-manifest.mjs` at line 381, Update the audit flow around
deriveMcpTools and undocumentedMcp so a null MCP result is treated as an audit
failure rather than converted to an empty list. Ensure MANIFEST_CLEAN and exit 0
are unreachable when MCP derivation returns null, while preserving normal
undocumented-tool handling for non-null results.
workflows/verify-features.ts (3)

1279-1285: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Cleanup pattern still broad enough to touch unrelated workspace state (unresolved from prior review).

grep -oE '(vf|cp[0-9])[A-Za-z0-9_-]*' still matches those substrings anywhere in the listed output, so a pre-existing agent/channel containing vf or cp<digit> can be removed/archived by this cleanup step.

🤖 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 `@workflows/verify-features.ts` around lines 1279 - 1285, Restrict the cleanup
loops for relay agents and channels to only match resources created by this
workflow, rather than matching vf or cp<digit> substrings anywhere in command
output. Update the grep patterns or parsing in both cleanup blocks so names are
matched against the complete expected resource-name format and unrelated
pre-existing resources are never removed or archived.

161-170: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Ledger integrity gap persists (unresolved from prior review).

record still only flattens \n\r\t; raw control/ANSI bytes from CLI output can land inside the JSON string. The verdict reader (1337-1354) still drops JSON.parse failures silently without counting them, so a corrupted ledger line can vanish rather than fail the run.

Also applies to: 1337-1354

🤖 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 `@workflows/verify-features.ts` around lines 161 - 170, Harden the ledger path
by updating record() to sanitize all control and ANSI escape bytes before
writing JSON, not only newline, carriage-return, and tab characters. Update the
verdict reader around JSON.parse to treat parse failures as recorded failures
instead of silently discarding corrupted lines, ensuring malformed ledger
entries cannot disappear without affecting the run result.

645-646: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

[ $? -le 124 ] still treats real failures as success (unresolved from prior review).

Exit 1 (e.g. "unknown command", broker unreachable) satisfies -le 124, so a removed/broken relay node tail still records PASS.

🤖 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 `@workflows/verify-features.ts` around lines 645 - 646, Update the “node tail
(bounded)” check in run_check so only a successful command exit or timeout
status 124 produces tail-ok; ensure exit codes such as 1 for command or
connectivity failures cause the check to fail rather than pass.
🧹 Nitpick comments (1)
workflows/verify-features.ts (1)

904-942: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Webhook token failure isn't fail-fast, unlike the rest of the file's established pattern.

If WEBHOOK_TOKEN extraction fails (empty), relay channel create for $WEBHOOK_CHANNEL is swallowed via || true, and the subsequent integration webhook create would then likely fail for an unrelated reason (missing channel) rather than reflecting a real webhook defect — misattributing the failure. Tier 3/4 already establish the pattern of checking token emptiness up front and skip_check-ing the dependent checks with a clear reason; this block doesn't follow it.

🤖 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 `@workflows/verify-features.ts` around lines 904 - 942, Update the webhook
setup around WEBHOOK_TOKEN and WEBHOOK_CHANNEL to validate that token extraction
succeeded before creating the channel or running dependent integration checks.
When WEBHOOK_TOKEN is empty, skip the webhook list, subscription, create,
trigger, and delete checks with a clear token-failure reason, while preserving
cleanup and the existing cloud-capability handling; only proceed with channel
creation and webhook operations when the token is present.
🤖 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 @.agentworkforce/features/manifest.yaml:
- Around line 848-849: Update the description for the “relay integration webhook
create” CLI entry in the manifest to describe it as registering an inbound
webhook, while preserving the existing command and channel behavior.

In `@CHANGELOG.md`:
- Line 8: Update the changelog’s top-level heading from “[Unreleased - Patch]”
to the standard “[Unreleased]” format, keeping release type labels reserved for
versioned release sections.

In `@scripts/audit-feature-manifest.mjs`:
- Around line 247-300: Update the audit’s exit-status decision to treat a
non-null staleBuildWarning() as a non-clean result, even when drift === 0.
Ensure stale builds fail closed or otherwise use the existing unauditable status
consumed by the workflow, while preserving current exit behavior for genuine
drift and clean, current builds.

In `@workflows/verify-features.ts`:
- Around line 1052-1057: Correct the down-state assertion in the CP1
verification flow after `relay node down` so it succeeds only when the complete
status output contains no “running” match, rather than when any individual line
lacks it. Update the `relay node status` pipeline around the final `grep` while
preserving the existing CP1 increment behavior and pipefail-safe redirection.

---

Duplicate comments:
In `@scripts/audit-feature-manifest.mjs`:
- Around line 192-204: Guard the `send` helper in the audit request flow against
`child.stdin` write failures, including EPIPE caused by spawn failure or
`child.kill()`, so they are captured by the existing `main().catch(...)` path
and produce the intended exit code 2. Attach appropriate stdin error handling
and propagate the failure through `send` without changing the JSON-RPC messages
or audit behavior.
- Line 381: Update the audit flow around deriveMcpTools and undocumentedMcp so a
null MCP result is treated as an audit failure rather than converted to an empty
list. Ensure MANIFEST_CLEAN and exit 0 are unreachable when MCP derivation
returns null, while preserving normal undocumented-tool handling for non-null
results.

In `@workflows/verify-features.ts`:
- Around line 1279-1285: Restrict the cleanup loops for relay agents and
channels to only match resources created by this workflow, rather than matching
vf or cp<digit> substrings anywhere in command output. Update the grep patterns
or parsing in both cleanup blocks so names are matched against the complete
expected resource-name format and unrelated pre-existing resources are never
removed or archived.
- Around line 161-170: Harden the ledger path by updating record() to sanitize
all control and ANSI escape bytes before writing JSON, not only newline,
carriage-return, and tab characters. Update the verdict reader around JSON.parse
to treat parse failures as recorded failures instead of silently discarding
corrupted lines, ensuring malformed ledger entries cannot disappear without
affecting the run result.
- Around line 645-646: Update the “node tail (bounded)” check in run_check so
only a successful command exit or timeout status 124 produces tail-ok; ensure
exit codes such as 1 for command or connectivity failures cause the check to
fail rather than pass.

---

Nitpick comments:
In `@workflows/verify-features.ts`:
- Around line 904-942: Update the webhook setup around WEBHOOK_TOKEN and
WEBHOOK_CHANNEL to validate that token extraction succeeded before creating the
channel or running dependent integration checks. When WEBHOOK_TOKEN is empty,
skip the webhook list, subscription, create, trigger, and delete checks with a
clear token-failure reason, while preserving cleanup and the existing
cloud-capability handling; only proceed with channel creation and webhook
operations when the token is present.
🪄 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: 1f820774-e7e1-4722-b33b-335d886d56b9

📥 Commits

Reviewing files that changed from the base of the PR and between 0f010e6 and fcbfd7f.

📒 Files selected for processing (8)
  • .agentworkforce/features/manifest.yaml
  • CHANGELOG.md
  • packages/cli/src/cli/commands/integration-webhook-create.test.ts
  • packages/cli/src/cli/commands/integration.ts
  • packages/sdk/src/__tests__/integrations.test.ts
  • packages/sdk/src/messaging/types.ts
  • scripts/audit-feature-manifest.mjs
  • workflows/verify-features.ts

Comment thread .agentworkforce/features/manifest.yaml Outdated
Comment thread CHANGELOG.md
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [Unreleased - Patch]

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use the standard [Unreleased] heading.

Unreleased must not embed a release type; reserve SemVer versions for released sections.

Proposed fix
-## [Unreleased - Patch]
+## [Unreleased]

As per coding guidelines, CHANGELOG.md must “maintain the correctly leveled [Unreleased] heading.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## [Unreleased - Patch]
## [Unreleased]
🤖 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 `@CHANGELOG.md` at line 8, Update the changelog’s top-level heading from
“[Unreleased - Patch]” to the standard “[Unreleased]” format, keeping release
type labels reserved for versioned release sections.

Source: Coding guidelines

Comment on lines +247 to +300
/**
* Warn when the built CLI is older than the sources it is derived from.
*
* The audit can only see what the built CLI exposes, so a stale `dist` makes it
* under-report and print MANIFEST_CLEAN while real commands go undocumented.
* That is exactly what happened here: a stale dist hid 16 shipped commands
* (`agent me`, `agent presence`, the whole `cloud room` and `cloud integration`
* trees) across several audit runs that all reported clean.
*
* @returns A warning string, or null when the build is current or unknowable.
*/
function staleBuildWarning() {
const distEntry = join(repoRoot, 'packages/cli/dist/cli/index.js');
const srcDir = join(repoRoot, 'packages/cli/src');

let distMtime;
try {
distMtime = statSync(distEntry).mtimeMs;
} catch {
return null; // auditing a published CLI, not this checkout
}

let newest = 0;
const walk = (dir) => {
let entries;
try {
entries = readdirSync(dir, { withFileTypes: true });
} catch {
return;
}
for (const entry of entries) {
const full = join(dir, entry.name);
if (entry.isDirectory()) {
if (entry.name !== 'node_modules' && entry.name !== 'dist') walk(full);
continue;
}
if (!entry.name.endsWith('.ts') || entry.name.includes('.test.')) continue;
try {
newest = Math.max(newest, statSync(full).mtimeMs);
} catch {
// unreadable file; ignore
}
}
};
walk(srcDir);

if (newest === 0 || newest <= distMtime) return null;
const minutes = Math.round((newest - distMtime) / 60_000);
return (
`packages/cli/dist is ${minutes} minute(s) older than packages/cli/src. ` +
`This audit only sees what the BUILT CLI exposes, so newer commands are invisible ` +
`and a "clean" result understates drift. Rebuild first: npm run build --workspace=agent-relay`
);
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file exists and size =="
wc -l scripts/audit-feature-manifest.mjs workflows/audit-feature-manifest.ts 2>/dev/null || true

echo "== relevant function and exit points =="
sed -n '220,320p' scripts/audit-feature-manifest.mjs
printf '\n--- exit/staleBuild usages ---\n'
rg -n "staleBuildWarning|staleBuild|process\.exit|audit-feature-manifest|mjs" scripts scripts workflows 2>/dev/null || true

echo "== workflow snippets =="
if [ -f workflows/audit-feature-manifest.ts ]; then
  sed -n '1,240p' workflows/audit-feature-manifest.ts
fi

Repository: AgentWorkforce/relay

Length of output: 25982


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '360,460p' scripts/audit-feature-manifest.mjs
printf '\n--- workflow audit result logic relevant lines ---\n'
sed -n '780,835p' workflows/audit-feature-manifest.ts
printf '\n--- staleBuild warning references in workflow artifacts/usages ---\n'
rg -n "staleBuildWarning|staleBuild|audit\.json|drift|exit code|exitCode" workflows/audit-feature-manifest.ts workflows 2>/dev/null || true

Repository: AgentWorkforce/relay

Length of output: 15655


Surface stale builds with the audit exit semantics

staleBuildWarning() is only printed and included in audit.json; when drift === 0 the script still exits 0. The workflow treats exit 0 as clean and ignores staleBuildWarning, so a stale dist can pass code-only gates even though the audit no longer reliably reflects the CLI surface.

One option: fail closed (or mark unauditable) without breaking existing drift paths
-  process.exit(drift === 0 ? 0 : 1);
+  if (staleBuild && !process.env.VERIFY_ALLOW_STALE_BUILD) {
+    process.exit(2);
+  }
+  process.exit(drift === 0 ? 0 : 1);
🤖 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 `@scripts/audit-feature-manifest.mjs` around lines 247 - 300, Update the
audit’s exit-status decision to treat a non-null staleBuildWarning() as a
non-clean result, even when drift === 0. Ensure stale builds fail closed or
otherwise use the existing unauditable status consumed by the workflow, while
preserving current exit behavior for genuine drift and clean, current builds.

Comment thread workflows/verify-features.ts Outdated
Codex and CodeRabbit findings on #1392. Every item is a real hole in code
this PR added; each is fixed and exercised.

Audit script:
- A version match is not provenance. Between releases a globally installed
  CLI reports the same version as the checkout, so the old gate passed while
  every check ran the global binary. It now resolves the binary and requires
  it to live inside this checkout; outside the checkout fails, and a non-git
  tree is a stated SKIP rather than a pass.
- An underivable MCP surface now exits 2. Previously mcpTools === null forced
  both MCP drift arrays empty, so a broken probe printed MANIFEST_CLEAN and
  exited 0 — half an audit reported as a clean one.
- Stale detection required only that SOME prefix of a documented command
  exist. Since deriveCliLeaves records every parent group, removing `fleet
  spawn` left `fleet` and hid the removal. It now requires the exact leaf,
  with positional-dispatch parents (`telemetry`) named explicitly.
- child.stdin now has an error handler. A broken pipe raised an uncaught
  'error' — not a rejection, so it escaped main().catch and exited 1, which
  the audit workflow reads as confirmed drift and acts on.

verify-features:
- The ledger stripped only \n\r\t, so ANSI/control bytes made a line
  unparseable and the verdict reader dropped it silently — a failing check
  could vanish. Control bytes are stripped, and unparseable lines are now
  counted and fail the run.
- `[ $? -le 124 ]` accepted exit 1, so a broken `node tail` scored PASS. Only
  0, 124 and 137 now count.
- CP1's down-assertion used `grep -v`, which succeeds when ANY line lacks the
  pattern; a multi-line status still saying running satisfied it. Negated
  over the whole output instead.
- Cleanup matched "vf"/"cp<digit>" anywhere in the listing and could remove
  unrelated workspace agents and channels. Now anchored to this run's SUFFIX.
- `cloud logs` masked a missing <runId> with `|| true` and recorded a false
  pass off the error text; it is a stated skip like `cloud status`.

audit workflow:
- AUDIT_AUTOFIX=0 only stopped file-issue. The editor still branched and
  committed, and open-pr could push it. The flag is written to autofix.env
  and honoured by the editor and the integrity gate.
- Both audit.json consumers parsed it unguarded, though it is written under
  `|| true` and can be truncated — drift with no alert. Both now degrade to a
  stated "report unreadable" message.
- The integrity gate only protected the audit script. It now asserts the whole
  changed-file set is the manifest alone, so drift cannot be resolved by
  deleting a command or relaxing the contract test's expectations.

manifest: the webhook entry described an outbound webhook; it is inbound.

Not applied: CodeRabbit asked for a plain `[Unreleased]` changelog heading.
CLAUDE.md requires the pending release level in the heading
(`[Unreleased - Patch]`), so the existing form is correct for this repo.

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

Copy link
Copy Markdown
Member Author

Thanks — all of these were real. Addressed in 1062989c5, with what I did and how I checked it.

Codex

P1 · Verify the checkout rather than matching only its version — Correct, and this is the one that mattered most: between releases a global relay reports the same version as the branch, so the gate passed while every check ran the global binary. provenance now resolves the binary with realpathSync and requires it to sit inside git rev-parse --show-toplevel. Verified both ways: same version + binary outside the checkout → FAIL; non-git tree → stated SKIP (falling back to $PWD would only prove "under the cwd", which isn't provenance).

P1 · Pass the file path and channel flag to upload — Already fixed in ec17d7ecf; you reviewed 72539f7b8. A live run hit it too. Worth noting the command is still broken for a different reason (#1398): the file is never uploaded, so the check stays red on purpose.

P1 · Honor VERIFY_AUTOFIX before launching the fixer — Already fixed in ec17d7ecf via autofix.env, honoured by attempt-fix and fix-integrity, so open-pr can't push.

P1 · Honor AUDIT_AUTOFIX before launching the editor — Was still open; fixed the same way.

P1 · Fail when the MCP surface cannot be derived — Agreed, and it's the exact failure mode this PR exists to stop. mcpTools === null now exits 2. Verified with a stub CLI that serves --help normally but dies on mcp: AUDIT_ERROR: MCP tool list could not be derived… Refusing to report a partial audit as clean → exit 2.

P1 · Require the full CLI leaf when detecting stale entries — Right, and worse than described: since deriveCliLeaves records every parent group, almost any removed child was hidden. Now requires an exact leaf, with positional-dispatch parents named explicitly in POSITIONAL_DISPATCH (telemetry). Verified: a documented fleet ghost under a surviving fleet is now reported stale, while telemetry enable still isn't.

P2 · Supply run IDs to cloud status and logscloud status was already a stated skip; cloud logs still recorded a false pass off its error text through || true. Now also a stated skip.

CodeRabbit

Guard child.stdin against EPIPE — Good catch on the consequence, not just the crash: an uncaught 'error' isn't a rejection, so it escaped main().catch, exited 1, and the workflow would read that as confirmed drift and dispatch the editor. Handler added.

Ledger integrity gap — Fixed on both sides. record strips all control bytes, and the verdict now counts unparseable lines and fails the run on them, rather than dropping them. Verified: injecting a truncated line yields FAIL … 1 unparseable ledger line(s) — check results were lost.

[ $? -le 124 ] treats real failures as success — Fixed to 0|124|137. Verified timeout 5 false now records tail-failed rc=1 where it previously passed.

grep -qiv running does not assert "broker is down" — Fixed to ! … | grep -i running.

Cleanup pattern too broad — Fixed; teardown is now anchored to this run's SUFFIX, so pre-existing workspace state can't match.

Unguarded JSON.parse(audit.json) — Both consumers now degrade to an explicit "report unreadable" message instead of an empty body swallowed by || true.

Integrity gate only protects the audit script — Agreed; asserting one file was the wrong shape. It now requires the entire changed-file set to be the manifest alone, so drift can't be resolved by deleting a command or relaxing manifest-contract.test.ts. The editor task was updated to match.

Describe this as an inbound webhook — Fixed.

Not applied

Use the standard [Unreleased] heading — Declining this one. CLAUDE.md in this repo requires the pending release level in the heading ([Unreleased - Patch] / - Minor / - Major, monotonic, reset on release). [Unreleased - Patch] is the correct form here.


Re-verified after the changes: typecheck clean, both workflows Validation: PASS, all 30 generated shell bodies pass sh -n, MANIFEST_CLEAN, 91 tests green.

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/audit-feature-manifest.mjs (1)

437-490: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Stale-build warning still doesn't affect the exit code (unresolved from prior review).

staleBuildWarning() is computed and included in the report/JSON, but Line 489 exits 0/1 purely on drift, ignoring it. The downstream workflow (workflows/audit-feature-manifest.ts) treats exit 0 as "manifest is clean" and skips Slack/issue filing, so a stale dist build can pass every gate even though the derived CLI surface may not reflect current source. This was flagged in a prior review round and, unlike the sibling EPIPE and MCP-null fixes in this same commit, does not appear to have been addressed here.

♻️ One option: fail closed (or mark unauditable) on a stale build
+  if (staleBuild && !process.env.VERIFY_ALLOW_STALE_BUILD) {
+    process.exit(2);
+  }
   process.exit(drift === 0 ? 0 : 1);
🤖 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 `@scripts/audit-feature-manifest.mjs` around lines 437 - 490, Update the audit
result and exit-status logic around staleBuild and the final process.exit call
so a stale build cannot produce a successful manifest audit. Treat staleBuild as
a failure or otherwise mark the audit unauditable, ensure the human-readable
status reflects that outcome, and preserve clean success only when drift is zero
and no stale-build condition exists.
🤖 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 `@workflows/verify-features.ts`:
- Around line 1396-1405: Update the ledger parsing flow in
workflows/verify-features.ts to retain every nonblank line, rather than
filtering only lines whose trimmed content starts with “{”. Pass each retained
line through JSON.parse so truncated or corrupted records increment
malformedLines and cannot be silently omitted.
- Around line 1097-1101: Update the CP1 status check in the relay node
verification flow so a failed relay node status command does not increment CP1
as “broker down.” Require relay node status to complete successfully before
evaluating whether its output lacks “running,” or explicitly accept only the
documented down-state exit code while treating other failures as errors.

---

Outside diff comments:
In `@scripts/audit-feature-manifest.mjs`:
- Around line 437-490: Update the audit result and exit-status logic around
staleBuild and the final process.exit call so a stale build cannot produce a
successful manifest audit. Treat staleBuild as a failure or otherwise mark the
audit unauditable, ensure the human-readable status reflects that outcome, and
preserve clean success only when drift is zero and no stale-build condition
exists.
🪄 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: 394920c0-7fed-484e-9ab9-fee654c66d75

📥 Commits

Reviewing files that changed from the base of the PR and between fcbfd7f and 1062989.

📒 Files selected for processing (4)
  • .agentworkforce/features/manifest.yaml
  • scripts/audit-feature-manifest.mjs
  • workflows/audit-feature-manifest.ts
  • workflows/verify-features.ts

Comment on lines +1097 to +1101
# "grep -v" succeeds when ANY line lacks the pattern, so a multi-line status
# that still says running satisfied it. Negate the match over the whole output.
if ! relay node status --state-dir "$CP1_STATE" 2>&1 | grep -i running >/dev/null; then
CP1=$((CP1 + 1))
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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not count a failed status command as “broker down.”

Line 1099 negates the entire pipeline, so an errored relay node status with no running text increments CP1. Require a successful status call before accepting the down assertion (or explicitly handle its documented down exit code).

Proposed fix
-if ! relay node status --state-dir "$CP1_STATE" 2>&1 | grep -i running >/dev/null; then
+CP1_STATUS=$(relay node status --state-dir "$CP1_STATE" 2>&1)
+CP1_STATUS_RC=$?
+if [ "$CP1_STATUS_RC" -eq 0 ] &&
+  ! printf '%s\n' "$CP1_STATUS" | grep -i -- running >/dev/null; then
   CP1=$((CP1 + 1))
 fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# "grep -v" succeeds when ANY line lacks the pattern, so a multi-line status
# that still says running satisfied it. Negate the match over the whole output.
if ! relay node status --state-dir "$CP1_STATE" 2>&1 | grep -i running >/dev/null; then
CP1=$((CP1 + 1))
fi
# "grep -v" succeeds when ANY line lacks the pattern, so a multi-line status
# that still says running satisfied it. Negate the match over the whole output.
CP1_STATUS=$(relay node status --state-dir "$CP1_STATE" 2>&1)
CP1_STATUS_RC=$?
if [ "$CP1_STATUS_RC" -eq 0 ] &&
! printf '%s\n' "$CP1_STATUS" | grep -i -- running >/dev/null; then
CP1=$((CP1 + 1))
fi
🤖 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 `@workflows/verify-features.ts` around lines 1097 - 1101, Update the CP1 status
check in the relay node verification flow so a failed relay node status command
does not increment CP1 as “broker down.” Require relay node status to complete
successfully before evaluating whether its output lacks “running,” or explicitly
accept only the documented down-state exit code while treating other failures as
errors.

Comment on lines +1396 to +1405
const raw = fs
.readFileSync(ledgerPath, 'utf8')
.split('\n')
.filter((line) => line.trim().startsWith('{'));
for (const line of raw) {
try {
checks.push(JSON.parse(line));
} catch {
malformedLines += 1;
}

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Parse every nonblank ledger line.

The { filter silently drops truncated or otherwise corrupted nonblank records before JSON.parse, leaving malformedLines at zero. A lost failing record can still yield a PASS when its tier has other valid entries.

Proposed fix
     .readFileSync(ledgerPath, 'utf8')
     .split('\n')
-    .filter((line) => line.trim().startsWith('{'));
+    .filter((line) => line.trim() !== '');
🤖 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 `@workflows/verify-features.ts` around lines 1396 - 1405, Update the ledger
parsing flow in workflows/verify-features.ts to retain every nonblank line,
rather than filtering only lines whose trimmed content starts with “{”. Pass
each retained line through JSON.parse so truncated or corrupted records
increment malformedLines and cannot be silently omitted.

CI caught two tests in relaycast-groups.test.ts still driving
`webhook create <url> --event`, which now fails with "unknown option
'--event'". My mistake: I changed a public CLI signature and verified only
the tests I had touched instead of the full suite.

- relaycast-groups.test.ts: both cases moved to `<channel> [--name]`.
- verify/procedures.md documented the same broken invocation, and built the
  whole webhook procedure on an outbound model the API does not have — it
  registered CAPTURE_URL as a delivery destination. `POST /v1/webhooks` is
  inbound only: it takes the channel and returns the URL external services
  post to. Corrected, and the surrounding note now states the inbound flow.

Full suite green: 1644 passed, 16 skipped. (integration-relayfile-contract
flaked once under full-suite ordering and passed standalone and on re-run; it
references nothing in this change.)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@khaliqgant
khaliqgant merged commit 8ee70db into main Jul 30, 2026
41 checks passed
@khaliqgant
khaliqgant deleted the feat/verify-features-hardening branch July 30, 2026 20:44
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