feat(workflows): add repo-triage — periodic maintenance via inline Haiku sub-agents#1293
feat(workflows): add repo-triage — periodic maintenance via inline Haiku sub-agents#1293
Conversation
Adds .archon/workflows/repo-triage.yaml: a self-contained periodic maintenance workflow that uses inline sub-agents (Claude SDK agents: field introduced in #1276) for map-reduce across open issues and PRs. Six DAG nodes, three-layer topology: - Layer 1 (parallel): triage-issues, link-prs, closed-pr-dedup-check, stale-nudge - Layer 2: closed-dedup-check (reads triage-issues state) - Layer 3: digest (synthesises all prior nodes + writes markdown) Capabilities per node: - triage-issues: delegates labeling to on-disk triage-agent; inline brief-gen Haiku for duplicate detection; 3-day auto-close clock for unanswered duplicate warnings - link-prs: conservative PR ↔ issue cross-refs via inline pr-issue- matcher Haiku, Sonnet re-verifies fully-addresses claims before suggesting Closes #X; auto-nudges on low-quality PR template fill with first-run grandfather guard (snapshot-only, no nudge spam) - closed-dedup-check: cross-matches open issues against recently- closed ones via inline closed-brief-gen Haiku; same 3-day clock - closed-pr-dedup-check: flags open PRs duplicating recently-closed PRs via inline pr-brief-gen Haiku; comment-only, never closes PRs - stale-nudge: 60-day inactivity pings (configurable); no auto-close - digest: synthesises per-node outputs + reads state files to emit $ARTIFACTS_DIR/digest.md with clickable GitHub comment links Env-gated rollout knobs: - DRY_RUN=1 (read-only; prints [DRY] lines, no gh/state mutations) - SKIP_PR_LINK=1, SKIP_CLOSED_DEDUP=1, SKIP_CLOSED_PR_DEDUP=1, SKIP_STALE_NUDGE=1 - STALE_DAYS=N (stale-nudge window; default 60) Cross-run state under .archon/state/ (gitignored): - triage-state.json briefs + pendingDedupComments - closed-dedup-state.json closedBriefs + closedMatchComments - closed-pr-dedup-state.json openBriefs + closedBriefs + matches - pr-state.json linkedPrs + commentIds + templateAdherence - stale-nudge-state.json nudged (with updatedAtAtNudge for re-nudge) Every bot comment: - @-tags the target human (reporter for issues, author for PRs) - Tracks comment ID in state for traceability - Is idempotent — re-runs skip existing comments Intended use: invoke periodically (`archon workflow run repo-triage --no-worktree`) once a scheduler lands; live state persists across runs so previously-flagged items reconcile correctly. .gitignore: adds .archon/state/ for cross-run memory files.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughAdds a new non-interactive GitHub Actions workflow Changes
Sequence Diagram(s)sequenceDiagram
participant Cron as Scheduler
participant GH as GitHub Actions
participant API as GitHub API
participant Agent as Triage agents / brief-gen
participant State as .archon/state (persist)
participant Art as Artifacts
participant Slack as Slack (optional)
Cron->>GH: trigger repo-triage
GH->>Agent: run node (triage-issues / link-prs / stale-nudge / closed-* / digest)
Agent->>API: fetch issues, PRs, templates, diffs
Agent->>State: read/write per-node JSON state (atomic merges)
Agent->>API: post labels/comments/cross-references (sequential, idempotent)
Note over Agent,State: 3-day windows and at-most-once actions tracked in state
GH->>Art: write `.digest.md` and per-run artifacts
GH->>Slack: optional best-effort summary via webhook
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.archon/workflows/repo-triage.yaml:
- Around line 826-828: The GH PR list invocation that writes to
"$ARTIFACTS_DIR/prs.json" is missing the isDraft field, so the draft-skip
guardrail cannot operate; update the gh pr list --json argument (the call that
currently requests number,title,body,headRefName,author,updatedAt) to also
include isDraft so the orchestrator can read draft status before applying the
draft skip logic that references prs.json.
- Around line 217-222: When auto-closing duplicates (the branch that checks "now
- postedAt >= 3 days" and posts the "Auto-closing: no reply within 3 days..."
comment then runs `gh issue close <N> --reason not_planned`), capture the GitHub
comment ID returned when posting the closing comment and persist it to a per-run
artifact/state bucket (e.g., an array like `runPostedCommentIds` or under
`digest.postedComments`) before removing the entry from the tracked
`state`/`pending` store; ensure the same change is applied to the other
auto-close paths (lines referenced 442-448 and 1176-1220) so the digest can
include a direct GitHub URL for every bot comment posted this run.
- Around line 1176-1185: The workflow currently uses a gh call ("gh repo view
--json nameWithOwner --jq .nameWithOwner") inside the digest step while
guardrails forbid gh calls; fix by either (A) removing the gh call and reading
the repo slug from a previously persisted value (pass the slug as an
input/artifact/state from an earlier job/node and reference that instead in the
digest step), or (B) explicitly allow a single read-only gh lookup by updating
the workflow guardrails to permit that command and keep the existing gh
invocation; ensure the chosen approach is applied consistently for the other
occurrences noted (the same digest / SLUG use around the later block).
- Around line 122-127: The triage workflow steps triage-issues and link-prs are
both writing to the shared artifact "$ARTIFACTS_DIR/issues.json" (produced by
the gh issue list call), causing concurrent clobbering and schema mismatch;
change the artifact output to a node-specific filename (e.g., include the
job/step name or matrix value) so each job writes its own file (replace
"$ARTIFACTS_DIR/issues.json" with a unique name like
"$ARTIFACTS_DIR/issues-triage-issues.json" or "$ARTIFACTS_DIR/issues-${{
github.job }}.json" for both the gh issue list invocation and any consumers) and
update downstream consumers to read the corresponding node-specific filename.
- Around line 1075-1086: The stale-PR filter is applying label-based exclusions
but the gh CLI invocation (the gh pr list that writes to
"$ARTIFACTS_DIR/stale-prs.json") did not request labels, so label-based checks
like `blocked`, `keep-open`, `wontfix`, `needs-maintainer`, `pinned` are
ineffective; update the `gh pr list --json ...` call to include `labels` (e.g.,
add "labels" to the --json fields) so the produced stale-prs.json contains
labels, then ensure the filter logic that inspects labels (the skip checks for
`isDraft` and the list of do-not-bother labels) uses those labels from the JSON.
- Around line 1095-1106: The idempotency check doesn't match the workflow's own
comment prefix — update the idempotency logic (the string used in the
`Idempotency:` check) to look for the actual posted prefix used in the template
(e.g. match comments that start with "@<author> this {issue,pr} has been quiet"
or use a regex that accepts either "@<author>" or "this {issue,pr} has been
quiet"); modify the Idempotency line in repo-triage.yaml so it checks for the
correct prefix used in the "Issues:" and "PRs:" templates to ensure the workflow
skips when its own comment already exists.
- Around line 981-985: The save logic currently overwrites each
state.linkedPrs[<pr>] with only { sha, processedAt, related, fullyAddresses },
dropping fields like commentIds, templateAdherence, and templateNudgedAt; change
the write/refresh step so it merges the new values into the existing linkedPrs
entry (preserving any existing keys) before assigning and writing
state.lastRunAt and .archon/state/pr-state.json, i.e., read the existing
state.linkedPrs[pr] and produce mergedEntry = { ...existingEntry, sha,
processedAt, related, fullyAddresses } (or equivalent merge operation) and write
mergedEntry back.
- Around line 1221-1226: Add a deterministic, shared run-start timestamp before
any comment filtering by inserting an initial workflow step (e.g., a step named
set-run-start) that captures a single ISO timestamp (now) and publishes it as a
step output and environment variable (e.g.,
steps.set-run-start.outputs.run_start / RUN_START); then change all subsequent
filtering logic that currently compares postedAt/nudgedAt against "today" or
local clocks to compare against that shared run_start value (use the step output
or env) so every node/step uses the exact same cutoff when deciding which
comments belong to "this run" vs. carry-forward pending.
- Line 221: Replace the incorrect gh CLI reason token by updating occurrences of
the command string 'gh issue close <N> --reason not_planned' to use the proper
quoted reason with a space: 'gh issue close <N> --reason "not planned"'; locate
both occurrences of the exact command text in the workflow and update them so
the CLI accepts the reason value.
- Line 499: The gh CLI invocations using "gh pr view <N> --json ..." include an
unsupported field "stateReason" (present in the three occurrences of the gh pr
view command) which breaks the pr-brief-gen task; remove "stateReason" from
those --json field lists and update any logic in the pr-brief-gen task that used
stateReason so it instead derives PR status from mergedAt and closedAt (treat
mergedAt !== null as "merged", else closedAt !== null as "closed unmerged").
Ensure you only adjust the three gh pr view --json invocations and the
status-derivation code path that referenced stateReason.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: e4cf7f34-e1f4-4378-9527-a131c1e39d40
📒 Files selected for processing (2)
.archon/workflows/repo-triage.yaml.gitignore
| ## 2. Fetch all open issues | ||
| ``` | ||
| gh issue list --state open \ | ||
| --json number,title,body,author,labels,comments,createdAt,updatedAt \ | ||
| --limit 200 > "$ARTIFACTS_DIR/issues.json" | ||
| ``` |
There was a problem hiding this comment.
Use node-specific artifact filenames to avoid concurrent clobbering.
triage-issues and link-prs run concurrently, but both write $ARTIFACTS_DIR/issues.json with different schemas. Either node can read the other node’s file or a partial redirect.
Proposed fix
gh issue list --state open \
--json number,title,body,author,labels,comments,createdAt,updatedAt \
- --limit 200 > "$ARTIFACTS_DIR/issues.json"
+ --limit 200 > "$ARTIFACTS_DIR/triage-issues.json"
...
gh issue list --state open \
--json number,title,body,labels,author \
- --limit 200 > "$ARTIFACTS_DIR/issues.json"
+ --limit 200 > "$ARTIFACTS_DIR/link-prs-issues.json"Also applies to: 824-833
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.archon/workflows/repo-triage.yaml around lines 122 - 127, The triage
workflow steps triage-issues and link-prs are both writing to the shared
artifact "$ARTIFACTS_DIR/issues.json" (produced by the gh issue list call),
causing concurrent clobbering and schema mismatch; change the artifact output to
a node-specific filename (e.g., include the job/step name or matrix value) so
each job writes its own file (replace "$ARTIFACTS_DIR/issues.json" with a unique
name like "$ARTIFACTS_DIR/issues-triage-issues.json" or
"$ARTIFACTS_DIR/issues-${{ github.job }}.json" for both the gh issue list
invocation and any consumers) and update downstream consumers to read the
corresponding node-specific filename.
| - Else if `now - postedAt >= 3 days`: | ||
| - Post closing comment on #N: | ||
| "Auto-closing: no reply within 3 days of the duplicate | ||
| check. Please reopen if this is still relevant." | ||
| - `gh issue close <N> --reason not_planned` | ||
| - Drop entry from state. |
There was a problem hiding this comment.
Capture auto-close comment IDs before dropping pending state.
Both auto-close paths post a closing comment and then remove the tracked entry, so the digest cannot satisfy “direct GitHub URL for every bot comment this run posted.” Store closing comment IDs in a per-run artifact/state bucket before deletion.
Also applies to: 442-448, 1176-1220
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.archon/workflows/repo-triage.yaml around lines 217 - 222, When auto-closing
duplicates (the branch that checks "now - postedAt >= 3 days" and posts the
"Auto-closing: no reply within 3 days..." comment then runs `gh issue close <N>
--reason not_planned`), capture the GitHub comment ID returned when posting the
closing comment and persist it to a per-run artifact/state bucket (e.g., an
array like `runPostedCommentIds` or under `digest.postedComments`) before
removing the entry from the tracked `state`/`pending` store; ensure the same
change is applied to the other auto-close paths (lines referenced 442-448 and
1176-1220) so the digest can include a direct GitHub URL for every bot comment
posted this run.
| and a `state` hint (open/closed) in the caller's prompt. | ||
|
|
||
| Fetch: | ||
| gh pr view <N> --json number,title,body,state,headRefName,changedFiles,additions,deletions,mergedAt,closedAt,stateReason |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -u
if ! command -v gh >/dev/null 2>&1; then
echo "gh CLI is not installed in this environment"
exit 0
fi
gh pr view --help | sed -n '/JSON Fields/,/FLAGS/p' | grep -E 'stateReason|mergedAt|closedAt|state' || trueRepository: coleam00/Archon
Length of output: 41
🌐 Web query:
GitHub gh pr view JSON fields stateReason documentation
💡 Result:
The gh pr view command supports JSON output via the --json flag. The official documentation lists the available fields explicitly. Available JSON fields for gh pr view (from https://cli.github.com/manual/gh_pr_view): additions, assignees, author, autoMergeRequest, baseRefName, baseRefOid, body, changedFiles, closed, closedAt, closingIssuesReferences, comments, commits, createdAt, deletions, files, fullDatabaseId, headRefName, headRefOid, headRepository, headRepositoryOwner, id, isCrossRepository, isDraft, labels, latestReviews, maintainerCanModify, mergeCommit, mergeStateStatus, mergeable, mergedAt, mergedBy, milestone, number, potentialMergeCommit, projectCards, projectItems, reactionGroups, reviewDecision, reviewRequests, reviews, state, statusCheckRollup, title, updatedAt, url. stateReason is NOT a supported field for gh pr view. Attempting gh pr view --json stateReason previously caused a panic (fixed in cli/cli#9307 merged July 2024), and it was explicitly excluded from PR commands because it is an Issue-only field in GitHub's GraphQL API. For comparison, gh issue view DOES support stateReason (along with assignees, author, body, closed, closedAt, closedByPullRequestsReferences, comments, createdAt, id, isPinned, labels, milestone, number, projectCards, projectItems, reactionGroups, state, title, updatedAt, url). To view available fields for any command, run gh pr view --json "" (or any invalid field) to see the error message listing them. Fields come from GitHub GraphQL PullRequest object but are a curated subset supported by the CLI.
Citations:
- 1: https://cli.github.com/manual/gh_pr_view
- 2: Fix panic when calling
gh pr view --json stateReasoncli/cli#9307 - 3:
gh pr view --json "stateReason"panics cli/cli#9301 - 4: https://cli.github.com/manual/gh_issue_view
- 5: https://developer.github.com/v4/object/issue/
- 6: https://docs.github.com/enterprise/2.13/developer/v4/enum/issuestate/
- 7: Merge JSON responses from
gh apicli/cli#8620 - 8: Add
--jsonexport flag for release list cli/cli#8474
🏁 Script executed:
# First, let's verify the file exists and check the specific lines mentioned
git ls-files | grep -E "repo-triage.yaml|\.archon"Repository: coleam00/Archon
Length of output: 3460
🏁 Script executed:
# Check the file size first
wc -l .archon/workflows/repo-triage.yaml
# Then view the specific lines mentioned
sed -n '495,510p' .archon/workflows/repo-triage.yaml
sed -n '625,635p' .archon/workflows/repo-triage.yamlRepository: coleam00/Archon
Length of output: 1513
Remove stateReason from PR gh JSON fields at lines 499, 507, and 632.
stateReason is not a supported field for gh pr view --json (it is an issue-only field in GitHub's GraphQL API). Using this field will cause the pr-brief-gen task to fail. Derive PR status ("merged" vs "closed unmerged") from the mergedAt and closedAt fields instead.
Proposed changes
- gh pr view <N> --json number,title,body,state,headRefName,changedFiles,additions,deletions,mergedAt,closedAt,stateReason
+ gh pr view <N> --json number,title,body,state,headRefName,changedFiles,additions,deletions,mergedAt,closedAt- "stateReason": "<closed reason if applicable, else null>",
+ "closeFlavour": "open | merged | closed-unmerged",- closed on <date> (<stateReason>). You may want to read
+ closed unmerged on <date>. You may want to read📝 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.
| gh pr view <N> --json number,title,body,state,headRefName,changedFiles,additions,deletions,mergedAt,closedAt,stateReason | |
| gh pr view <N> --json number,title,body,state,headRefName,changedFiles,additions,deletions,mergedAt,closedAt |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.archon/workflows/repo-triage.yaml at line 499, The gh CLI invocations using
"gh pr view <N> --json ..." include an unsupported field "stateReason" (present
in the three occurrences of the gh pr view command) which breaks the
pr-brief-gen task; remove "stateReason" from those --json field lists and update
any logic in the pr-brief-gen task that used stateReason so it instead derives
PR status from mergedAt and closedAt (treat mergedAt !== null as "merged", else
closedAt !== null as "closed unmerged"). Ensure you only adjust the three gh pr
view --json invocations and the status-derivation code path that referenced
stateReason.
| gh pr list --state open \ | ||
| --json number,title,body,headRefName,author,updatedAt \ | ||
| --limit 100 > "$ARTIFACTS_DIR/prs.json" |
There was a problem hiding this comment.
Fetch isDraft before using the draft skip.
The template nudge pass says to skip draft PRs, but the open PR fetch does not include isDraft, so the orchestrator has no fetched signal to enforce that guardrail.
Proposed fix
gh pr list --state open \
- --json number,title,body,headRefName,author,updatedAt \
+ --json number,title,body,headRefName,author,updatedAt,isDraft \
--limit 100 > "$ARTIFACTS_DIR/prs.json"Also applies to: 946-949
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.archon/workflows/repo-triage.yaml around lines 826 - 828, The GH PR list
invocation that writes to "$ARTIFACTS_DIR/prs.json" is missing the isDraft
field, so the draft-skip guardrail cannot operate; update the gh pr list --json
argument (the call that currently requests
number,title,body,headRefName,author,updatedAt) to also include isDraft so the
orchestrator can read draft status before applying the draft skip logic that
references prs.json.
| Stale open PRs (skip drafts): | ||
| gh pr list --state open --limit 100 \ | ||
| --json number,title,author,updatedAt,isDraft \ | ||
| --search "updated:<${CUTOFF}" > "$ARTIFACTS_DIR/stale-prs.json" | ||
|
|
||
| ## 3. Filter | ||
| For each item: | ||
| - Skip PRs where `isDraft == true` — drafts are often WIP, nudging | ||
| them is rude. | ||
| - Skip any item with a label matching `wontfix`, `blocked`, | ||
| `needs-maintainer`, `pinned`, `keep-open` (common "do not bother" | ||
| signals). Check current labels via the fetched JSON. |
There was a problem hiding this comment.
Include PR labels before applying do-not-bother filters.
The stale filter applies labels to “any item,” but stale PRs are fetched without labels, so PRs labeled blocked, keep-open, etc. can still be nudged.
Proposed fix
Stale open PRs (skip drafts):
gh pr list --state open --limit 100 \
- --json number,title,author,updatedAt,isDraft \
+ --json number,title,author,updatedAt,isDraft,labels \
--search "updated:<${CUTOFF}" > "$ARTIFACTS_DIR/stale-prs.json"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.archon/workflows/repo-triage.yaml around lines 1075 - 1086, The stale-PR
filter is applying label-based exclusions but the gh CLI invocation (the gh pr
list that writes to "$ARTIFACTS_DIR/stale-prs.json") did not request labels, so
label-based checks like `blocked`, `keep-open`, `wontfix`, `needs-maintainer`,
`pinned` are ineffective; update the `gh pr list --json ...` call to include
`labels` (e.g., add "labels" to the --json fields) so the produced
stale-prs.json contains labels, then ensure the filter logic that inspects
labels (the skip checks for `isDraft` and the list of do-not-bother labels) uses
those labels from the JSON.
| Issues: | ||
| @<author> this issue has been quiet for <N> days. Is it still | ||
| relevant? A quick update on current status would help with | ||
| triage. No reply needed if it's no longer blocking you. | ||
|
|
||
| PRs: | ||
| @<author> this PR has been quiet for <N> days. Is it still | ||
| active? Happy to help unblock if review feedback or a rebase | ||
| is needed — just drop a note. | ||
|
|
||
| Idempotency: `gh {issue,pr} view <N> --json comments` — skip if any | ||
| existing comment already starts with "this {issue,pr} has been quiet". |
There was a problem hiding this comment.
Match the actual stale-nudge comment prefix during idempotency checks.
The posted bodies start with @<author>, but the idempotency check looks for comments starting with "this {issue,pr} has been quiet", so it will not match this workflow’s own comments if state is missing or reset.
Proposed fix
- existing comment already starts with "this {issue,pr} has been quiet".
+ existing bot comment contains "this {issue,pr} has been quiet for"
+ or exactly matches the generated body after the leading `@mention`.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.archon/workflows/repo-triage.yaml around lines 1095 - 1106, The idempotency
check doesn't match the workflow's own comment prefix — update the idempotency
logic (the string used in the `Idempotency:` check) to look for the actual
posted prefix used in the template (e.g. match comments that start with
"@<author> this {issue,pr} has been quiet" or use a regex that accepts either
"@<author>" or "this {issue,pr} has been quiet"); modify the Idempotency line in
repo-triage.yaml so it checks for the correct prefix used in the "Issues:" and
"PRs:" templates to ensure the workflow skips when its own comment already
exists.
| # Comment-URL index — REQUIRED | ||
|
|
||
| The digest MUST include a direct GitHub URL for every bot comment | ||
| this run posted. Build URLs from state files. | ||
|
|
||
| Steps: | ||
|
|
||
| 1. Determine the repo slug: | ||
| SLUG=$(gh repo view --json nameWithOwner --jq .nameWithOwner) | ||
| (example: `coleam00/Archon`) |
There was a problem hiding this comment.
Resolve the digest gh call contradiction.
The digest requires gh repo view to build comment URLs, but its guardrails say “No gh calls here.” Either allow this single read-only lookup or pass the repo slug from an earlier node/artifact.
Two acceptable directions
- - No gh calls here. No comments. No closes. Synthesis only.
+ - No mutating gh calls here. A single read-only `gh repo view` is
+ allowed only to determine the repository slug for comment URLs.Or avoid gh entirely by having an earlier node persist the slug into state/artifacts.
Also applies to: 1333-1335
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.archon/workflows/repo-triage.yaml around lines 1176 - 1185, The workflow
currently uses a gh call ("gh repo view --json nameWithOwner --jq
.nameWithOwner") inside the digest step while guardrails forbid gh calls; fix by
either (A) removing the gh call and reading the repo slug from a previously
persisted value (pass the slug as an input/artifact/state from an earlier
job/node and reference that instead in the digest step), or (B) explicitly allow
a single read-only gh lookup by updating the workflow guardrails to permit that
command and keep the existing gh invocation; ensure the chosen approach is
applied consistently for the other occurrences noted (the same digest / SLUG use
around the later block).
| 5. Only surface comments posted IN THIS RUN. Use the `postedAt` / | ||
| `nudgedAt` timestamps: include entries whose timestamp equals | ||
| today's run window (≥ this run's start time). Older entries | ||
| from prior runs should NOT re-appear in the "just posted" tables | ||
| but DO appear in a separate "carry-forward pending" table so | ||
| maintainers see what's still on the 3-day clock. |
There was a problem hiding this comment.
Persist a run-start timestamp before filtering “this run” comments.
The digest is told to include comments with timestamps >= this run's start time, but no node records a shared run start. Same-day prior comments can leak into “this run,” or freshly posted comments can be missed if the digest guesses.
Proposed fix
+ At workflow start, persist a shared `runStartedAt` ISO timestamp
+ (for example in `$ARTIFACTS_DIR/run-started-at.txt` or each state
+ file's per-run metadata). The digest must use that value when
+ separating "this run" from carry-forward entries.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.archon/workflows/repo-triage.yaml around lines 1221 - 1226, Add a
deterministic, shared run-start timestamp before any comment filtering by
inserting an initial workflow step (e.g., a step named set-run-start) that
captures a single ISO timestamp (now) and publishes it as a step output and
environment variable (e.g., steps.set-run-start.outputs.run_start / RUN_START);
then change all subsequent filtering logic that currently compares
postedAt/nudgedAt against "today" or local clocks to compare against that shared
run_start value (use the step output or env) so every node/step uses the exact
same cutoff when deciding which comments belong to "this run" vs. carry-forward
pending.
…is set Extends the digest node with an optional Slack-post step after the canonical digest.md artifact is written. Uses Slack incoming webhook (no bot token required beyond the incoming-webhook scope). Behavior: - SLACK_WEBHOOK unset → skipped silently with a one-line note - DRY_RUN=1 → prints full payload, does not curl - Otherwise → POSTs a compact (<3500 char) mrkdwn-formatted summary containing headline numbers, this-run comment index (clickable GitHub URLs), pending items, and a path reference to digest.md - curl failure or non-ok Slack response is logged but does not fail the node — digest.md on disk remains authoritative - Intermediate Slack text written to $ARTIFACTS_DIR/digest-slack.txt for traceability; payload JSON assembled via jq and written to $ARTIFACTS_DIR/slack-payload.json before curl posts it Slack mrkdwn conversion rules baked into the prompt (no tables, link shape <url|text>, single-asterisk bold) so Sonnet emits a variant that renders cleanly in Slack rather than being sent raw. The webhook URL is read from the operator's environment (Archon auto-loads ~/.archon/.env on CLI startup — put SLACK_WEBHOOK=... there).
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
.archon/workflows/repo-triage.yaml (1)
1415-1417: Harden the Slackcurlinvocation.Two small but worthwhile tweaks on the POST:
- No timeout — a slow/unreachable webhook can stall the digest node indefinitely. Add
--max-time(and optionally--connect-timeout).- Prefer
--data-binary@file`` for JSON payloads;--datais safe here because `jq -c` emits a single line, but `--data-binary` is the idiomatic, byte-preserving choice and avoids surprises if the payload generation ever changes.♻️ Proposed fix
- curl -sS -X POST -H 'Content-Type: application/json' \ - --data "@$ARTIFACTS_DIR/slack-payload.json" \ - "$SLACK_WEBHOOK" + curl -sS --connect-timeout 5 --max-time 15 \ + -X POST -H 'Content-Type: application/json' \ + --data-binary "@$ARTIFACTS_DIR/slack-payload.json" \ + "$SLACK_WEBHOOK"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.archon/workflows/repo-triage.yaml around lines 1415 - 1417, The curl POST that sends "$SLACK_WEBHOOK" should be hardened: replace the current --data "@$ARTIFACTS_DIR/slack-payload.json" usage with --data-binary "@$ARTIFACTS_DIR/slack-payload.json" to preserve bytes, and add timeout flags such as --max-time 10 (and optionally --connect-timeout 5) to avoid stalling; update the curl invocation that references ARTIFACTS_DIR and SLACK_WEBHOOK accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.archon/workflows/repo-triage.yaml:
- Line 1337: The workflow is printing the SLACK_WEBHOOK secret; change the echo
that references SLACK_WEBHOOK to emit only whether it's set (e.g.,
"SLACK_WEBHOOK_SET=true/false") and remove any direct printing of the URL, and
update the failure logging that uses curl -sS (the curl invocation/its error
handling block) to scrub the webhook URL from any output before echoing (replace
or strip the SLACK_WEBHOOK value from messages so only a redacted token/boolean
appears).
---
Nitpick comments:
In @.archon/workflows/repo-triage.yaml:
- Around line 1415-1417: The curl POST that sends "$SLACK_WEBHOOK" should be
hardened: replace the current --data "@$ARTIFACTS_DIR/slack-payload.json" usage
with --data-binary "@$ARTIFACTS_DIR/slack-payload.json" to preserve bytes, and
add timeout flags such as --max-time 10 (and optionally --connect-timeout 5) to
avoid stalling; update the curl invocation that references ARTIFACTS_DIR and
SLACK_WEBHOOK accordingly.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: b9bf5947-0c29-4a4b-bef3-3d6291635ba6
📒 Files selected for processing (1)
.archon/workflows/repo-triage.yaml
|
|
||
| AFTER writing `digest.md`, check the env: | ||
|
|
||
| echo "SLACK_WEBHOOK=${SLACK_WEBHOOK:-<unset>}" |
There was a problem hiding this comment.
Don't echo SLACK_WEBHOOK — it's a bearer credential.
echo "SLACK_WEBHOOK=${SLACK_WEBHOOK:-<unset>}" prints the full webhook URL to stdout whenever it's set. Slack incoming webhook URLs are secrets — anyone with the URL can post to the channel — and this workflow runs outside GitHub Actions' automatic secret-masking, so the value will land in CI logs / digest artifacts verbatim. The same risk applies to the failure line at line 1429, since curl -sS will emit the target URL in network-error messages.
Emit a boolean only, and scrub URLs from any error you surface.
🔒 Proposed fix
AFTER writing `digest.md`, check the env:
- echo "SLACK_WEBHOOK=${SLACK_WEBHOOK:-<unset>}"
+ # Never print the webhook itself — it's a bearer credential.
+ if [ -n "${SLACK_WEBHOOK:-}" ]; then
+ echo "SLACK_WEBHOOK=<set>"
+ else
+ echo "SLACK_WEBHOOK=<unset>"
+ fiAnd for the failure-log instruction around line 1429, scrub the webhook from curl output before printing, e.g.:
- Slack post FAILED: <curl error or Slack response body>
+ Slack post FAILED: <curl error or Slack response body, with
+ the webhook URL redacted — never print $SLACK_WEBHOOK verbatim>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.archon/workflows/repo-triage.yaml at line 1337, The workflow is printing
the SLACK_WEBHOOK secret; change the echo that references SLACK_WEBHOOK to emit
only whether it's set (e.g., "SLACK_WEBHOOK_SET=true/false") and remove any
direct printing of the URL, and update the failure logging that uses curl -sS
(the curl invocation/its error handling block) to scrub the webhook URL from any
output before echoing (replace or strip the SLACK_WEBHOOK value from messages so
only a redacted token/boolean appears).
PR Review Summary (multi-agent)Agents run: Critical Issues (3) — must fix before merge
Important Issues (7) — fix before live runs
Suggestions (7)
Documentation Impact
Strengths
VerdictNEEDS FIXES — three critical issues ( Recommended Actions
|
Critical (3):
- `gh issue close --reason "not planned"` (space, not underscore) — the
CLI expects lowercase with a space; `not_planned` fails at runtime.
Fixed in both auto-close paths (triage-issues step 8, closed-dedup-
check step 7).
- link-prs step 7 state save was sparse `{ sha, processedAt, related,
fullyAddresses }`, overwriting `commentIds` / `templateNudgedAt` /
`templateAdherence`. Changed to explicit merge that spreads existing
entry first so per-run captured fields survive.
- Corrupt-JSON state files previously treated as first-run default
(silent `pendingDedupComments` reset → 3-day clock restarts forever).
All five state-load sites now abort loudly on JSON.parse throw;
ENOENT/empty continue to default-shape.
Important (7):
- Sub-agents (`brief-gen`, `closed-brief-gen`, `pr-brief-gen`,
`pr-issue-matcher`) emit `ERROR: <reason>` on gh failures rather than
partial/fabricated JSON. Orchestrator detects the sentinel, logs the
failed ID + first 200 chars of raw response, tracks in a failed-list,
and aborts the cluster/match pass if ≥50% of items failed (avoids
acting on bad data).
- `pr-brief-gen` now sets `diffTruncated: true` when the 30k-char diff
cap hits; link-prs verify pass downgrades any `fully-addresses` claim
to `related` when either side's brief was truncated.
- 3-day auto-close validates `postedAt` parses as ISO-8601 before the
elapsed-time comparison; corrupt timestamps are logged and skipped,
never acted on.
- `gh issue close` failure path no longer drops state — sets
`closeAttemptFailed: true` on the entry for next-run retry. Only
drops on exit 0.
- `closed-pr-dedup-check` idempotency check (`gh pr view --json comments`)
now aborts the post on fetch failure rather than falling through —
prevents double-posts on gh hiccups.
- `triage-agent` label pass has preflight `test -f` check for
`.claude/agents/triage-agent.md`; skips the pass with a clear log if
the file is missing rather than firing Task calls that fail obscurely.
- `brief-gen` template-adherence wording flipped from "Ignore … as
'filled'" (ambiguous, read as affirmative) to explicit "A section
counts as MISSING when …", matching the `pr-issue-matcher` phrasing.
Minor:
- `stale-nudge` idempotency check uses substring "has been quiet for"
instead of a prefix check that never matched (posted body starts
with @<author>).
- `closed-dedup-check` distinguishes "upstream crashed" (missing/corrupt
triage-state.json, or `lastRunAt == null`) from "legitimately quiet
day" (state present, briefs empty) — different log lines.
- Slack curl adds `-w "\nHTTP_STATUS:%{http_code}"` + `2>&1` so TLS /
4xx / 5xx errors are visible in captured output.
- `stateReason` values from `gh issue view --json stateReason` are
UPPERCASE (`COMPLETED`, `NOT_PLANNED`); documented and instruct
sub-agent to normalize to lowercase for consistency.
Docs:
- CLAUDE.md repo-level `.archon/` tree now lists `state/`.
- archon-directories.md tree adds `state/` + `scripts/` (both were
missing) with purpose descriptions.
Deferred (worth doing as a follow-up, not blocking):
- DRY/SKIP preamble duplication (~30-50 lines across 5 nodes).
- Explicit `BASELINE_IS_EMPTY` capture in link-prs (current derived
check works but is a load-bearing model instruction).
- Digest `WARNING` prefix block when upstream nodes are missing
outputs — today's "(output unavailable)" sub-line is functional.
- Pre-existing README workflow-count (17 → 20) and table gaps — not
caused by this PR.
Summary
agents:field from feat(workflows): inline sub-agent definitions on DAG nodes #1276 — proves the map-reduce pattern works end-to-end..archon/workflows/repo-triage.yaml— a 6-node DAG workflow. Adds.archon/state/to.gitignorefor the cross-run memory files the workflow writes.agents:support merged in feat(workflows): inline sub-agent definitions on DAG nodes #1276.UX Journey
Before
```
maintainer manual triage pass
────────── ────────────────────
open GitHub ────────────▶ eyeball 80 issues ──▶ apply labels one-by-one
spot obvious duplicates
(miss subtle ones)
nudge stale items (rarely)
notice a PR "Closes #X"
was never written
```
After
```
scheduler (future) repo-triage workflow maintainer
────────────── ───────────────────── ──────────
cron / cmdtrigger ───────▶ archon workflow run ──────────────▶ reads digest.md,
repo-triage clicks links to
│ specific bot
├─ Haiku fan-out briefs all comments
│ 80 issues + 56 PRs in parallel
├─ Sonnet reduces:
│ labels (delegated to triage-agent)
│ dedup clusters (open↔open)
│ closed-issue matches
│ closed-PR duplicates
│ stale-nudges (60d inactivity)
│ PR template-fill nudges
├─ idempotent GitHub comments
│ @-tagged to the right human
└─ digest.md with clickable
links to every comment
```
Architecture Diagram
Before
```
(no workflow existed — maintenance was manual)
```
After
```
DAG LAYERS
─────────────────────────────────────────────────────────────────
Layer 1 (parallel, no deps):
┌────────────────────┐ ┌────────────────────┐
│ triage-issues │ │ link-prs │
│ │ │ │
│ inline brief-gen │ │ inline pr-issue- │
│ (Haiku) │ │ matcher (Haiku) │
│ │ │ │
│ + delegates to │ │ + Sonnet verifies │
│ on-disk │ │ "fully-addr." │
│ triage-agent │ │ + first-run │
│ │ │ grandfather │
│ │ │ guard for │
│ │ │ template nudges │
│ writes state ═══▶ │ │ │
└────────────────────┘ └────────────────────┘
┌────────────────────┐ ┌────────────────────┐
│ closed-pr-dedup- │ │ stale-nudge │
│ check │ │ │
│ │ │ no Task fan-out │
│ inline pr-brief- │ │ (direct Sonnet │
│ gen (Haiku) │ │ work) │
│ │ │ │
│ comment only, │ │ 60-day inactivity │
│ never closes PRs │ │ pings; never │
│ │ │ closes │
└────────────────────┘ └────────────────────┘
Layer 2 (depends_on: triage-issues):
┌──────────────────────────────────────────┐
│ closed-dedup-check │
│ │
│ reads triage-state.json open briefs ◀══╗│
│ inline closed-brief-gen (Haiku) │
│ 3-day auto-close clock on matches │
└──────────────────────────────────────────┘
Layer 3 (depends_on: all five above):
┌──────────────────────────────────────────┐
│ digest │
│ │
│ reads $.output + all 5 state │
│ files; resolves repo slug via gh; │
│ writes $ARTIFACTS_DIR/digest.md with │
│ headline numbers, per-node summaries, │
│ comment-URL index (clickable), carry- │
│ forward items still on 3-day clock │
└──────────────────────────────────────────┘
```
Connection inventory:
.archon/workflows/options.agents.claude/agents/triage-agent.mdTaskfrom triage-issues.archon/state/*.json$ARTIFACTS_DIR/digest.mdLabel Snapshot
risk: low(workflow YAML only; no engine changes; DRY_RUN + SKIP_* flags bound blast radius)size: Mworkflowsworkflows:defaults(user-level workflow)Change Metadata
featureworkflowsLinked Issue
agents:feature merged in feat(workflows): inline sub-agent definitions on DAG nodes #1276Validation Evidence (required)
```bash
bun run cli validate workflows repo-triage # passes — 6 nodes, schema-valid
bun run validate # full suite green (no code changes)
```
Live run evidence (on dev with #1276 merged):
Security Impact (required)
ghfrom the operator's shell — inherits the operator's existing GitHub auth. Required scopes: issue/PR read + comment + label edit + close.ghCLI (github.com). No new endpoints beyond what manual triage would hit.ghauth lives in the operator's keychain already..archon/state/*.json(gitignored). Reads.github/pull_request_template.mdand.github/ISSUE_TEMPLATE/*.mdif present. Writes to$ARTIFACTS_DIR/*.Compatibility / Migration
DRY_RUN,SKIP_PR_LINK,SKIP_CLOSED_DEDUP,SKIP_CLOSED_PR_DEDUP,SKIP_STALE_NUDGE,STALE_DAYS.Human Verification (required)
Verified scenarios:
(DRY RUN)markers, per-node summaries readablebotCommentIdor nestedcommentIds)Edge cases checked:
closed-dedup-checkwith missingtriage-state.json(dry-run case) → gracefully reports "skipped, no open briefs"pr-template.md/issue-templates.mdand returnsno-template-contextin briefs (no false nudges)dag-node-skillsID collision via inline agents: warn log + platform message (from feat(workflows): inline sub-agent definitions on DAG nodes #1276)What was not verified:
--limit 200/--limit 100explicitlySide Effects / Blast Radius (required)
.archon/state/and.archon/artifacts/runs/<runId>/.DRY_RUN=1for safe previewSKIP_*=1flags for staged rollout (used SKIP_PR_LINK once, never needed again)in:comments author:<bot> ArchonRollback Plan (required)
rm .archon/workflows/repo-triage.yamllocally and don't invoke it again..archon/state/*.jsonhas everybotCommentId).gh api --method DELETE /repos/<owner>/<repo>/issues/comments/<id>per stored ID.SKIP_*=1env vars disable individual nodes instantly.dag.unsupported_capabilitiesif a future provider change strips inline-agents support; comments stop posting + state stops updating + digest marks affected nodes as(output unavailable).Risks and Mitigations
postedAtremoves the entry without closing; (3) closed issues can be reopened trivially; (4) close reason is set tonot_plannedso history is clear.ghauth to be configured on the operator's machine; silent failure if not.ghreturns an auth error or rate limit, stop the run cleanly and report the error in the summary. Do NOT partially mutate state." Verified in live run.Summary by CodeRabbit
New Features
Chores
Documentation