fix(factory): tolerate sparse/stub synced issues + canary regression check#10
Conversation
…ion check
Freshly-synced Linear issues land as a change-event STUB at the primary
/linear/issues/<key>__<uuid>.json path (only created/path/externalId/ts/id),
while the full — but sparse (no state.id/team/labels) — body lands at the
by-id/by-uuid aliases. Triage read only the stub, so every fresh issue read as
"live state is not ready-for-agent" and was never dispatched (live AR-305).
- readLinearIssueWithCanonicalFallback(): when the primary parses without usable
state, re-read the canonical by-id/by-uuid sibling (re-parsed against the
original primary path so key/uuid/path stay primary-anchored). Wired into
#readIssue and the CLI read paths (readIssueArg, isAllowedFactoryDraft).
- createFactory's fallback state resolver now seeds the name->UUID map from
config.linear.states, so sparse records (state.name, no state.id) resolve a
role without the /linear/states catalog.
- New `factory canary <issue>` CLI: runs the real dry-run triage path and asserts
a known issue is dispatch-ready (not skipped) — the regression detector for
sync-fidelity drift. Exits non-zero with the skip reason.
- Golden test reproducing the stub-primary + sparse-canonical shape.
Verified live: `factory canary AR-305` => {ok:true, status:"dispatched"}.
512 tests pass.
|
Warning Review limit reached
More reviews will be available in 42 minutes and 1 second. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
✨ 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.
Code Review
This pull request introduces a new factory-canary CLI command to act as a regression detector for sync-fidelity drift, ensuring known issues are correctly classified. It also implements readLinearIssueWithCanonicalFallback to handle sparse synced records (stubs) by falling back to canonical by-id or by-uuid paths. Feedback suggests validating the parsed key in canonicalIssueRecordPaths against ISSUE_KEY_PATTERN to prevent generating invalid lookup paths when processing by-uuid aliases.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const canonicalIssueRecordPaths = (path: string): string[] => { | ||
| const key = keyFromPath(path) | ||
| const uuid = uuidFromPath(path) | ||
| return [ | ||
| ...(key ? [linearByIdPath(key)] : []), | ||
| ...(uuid ? [linearByUuidPath(uuid)] : []), | ||
| ].filter((candidate) => candidate !== path) | ||
| } |
There was a problem hiding this comment.
In canonicalIssueRecordPaths, if the path is a by-uuid alias (e.g., /linear/issues/by-uuid/uuid-7.json), keyFromPath will return uuid-7. This results in generating an invalid lookup path /linear/issues/by-id/uuid-7.json since uuid-7 is not a valid human-readable Linear key.\n\nTo avoid unnecessary network/I/O requests to the mount client for invalid paths, we should validate that the parsed key matches the expected ISSUE_KEY_PATTERN before adding the by-id path.
const canonicalIssueRecordPaths = (path: string): string[] => {\n const key = keyFromPath(path)\n const uuid = uuidFromPath(path)\n return [\n ...(key && ISSUE_KEY_PATTERN.test(key) ? [linearByIdPath(key)] : []),\n ...(uuid ? [linearByUuidPath(uuid)] : []),\n ].filter((candidate) => candidate !== path)\n}There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eb3a1e8653
ℹ️ 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".
| for (const candidate of canonicalIssueRecordPaths(path)) { | ||
| try { | ||
| const canonical = await mount.readFile(candidate) | ||
| const parsed = parseLinearIssue(path, canonical.content) |
There was a problem hiding this comment.
Use canonical content for real writeback guards
When the primary issue file is a stub, this parses the by-id/by-uuid content but deliberately keeps issue.path pointing at the stub primary. In a real dispatch (dryRun: false), MountLinearWriteback.canonicalForIssue re-reads issue.path for its guard (src/writeback/linear.ts:166-179) and rejects that stub because it still has no title/team, so postComment/setState fail even though triage passed; the added dry-run path won't catch this production failure. The fallback needs to make the writeback guard read/accept the canonical payload as well, or real stub-primary issues remain undispatchable.
Useful? React with 👍 / 👎.
Review: PR #10 —
|
Problem
Freshly-synced Linear issues are no longer dispatched. The active-issues sync now writes a change-event STUB to the primary
/linear/issues/<key>__<uuid>.jsonpath (onlycreated/path/externalId/ts/id), while the full — but sparse (nostate.id/team/labels) — body lands at theby-id/by-uuidaliases. Factory triage read only the stub, so every fresh issue parsed asstateId=''→ "live state is not ready-for-agent" → never dispatched. Reproduced live with AR-305.This is the factory-side tolerance + detection layer. The upstream root cause (Nango
fetch-active-issuesdroppingstate.id/team/labels) is fixed separately; this makes the factory robust to sparse records regardless.Changes
readLinearIssueWithCanonicalFallback()— when the primary path parses without usable state (a stub), re-read the canonicalby-id/by-uuidsibling, re-parsed against the original primary path sokey/uuid/pathstay primary-anchored (dedup/dispatch keying unchanged). Wired into#readIssueand the CLI read paths (readIssueArg,isAllowedFactoryDraft).createFactory's fallback resolver now seeds the name→UUID map fromconfig.linear.states, so a record with onlystate.nameresolves a role without needing the/linear/statescatalog.factory canary <issue>CLI — runs the real dry-run triage path and asserts a known issue is dispatch-ready (not skipped); exits non-zero with the skip reason. This is the regression detector for sync-fidelity drift — wire it into CI/cron against a standing "Ready for Agent" canary issue.Verification
factory canary AR-305against the live mount →{ok:true, issue:"AR-305", status:"dispatched"}.🤖 Generated with Claude Code
Summary by cubic
Fixes dispatch failures for freshly synced Linear issues by reading canonical
by-id/by-uuidrecords when the primary file is a stub, and adds afactory canaryCLI to catch regressions early.Bug Fixes
readLinearIssueWithCanonicalFallback()to re-read canonical aliases when/linear/issues/<key>__<uuid>.jsonparses as a stub; keeps the original path/key/uuid for dedupe and dispatch. Wired into#readIssue,readIssueArg, and the draft-allowlist check.state.nameare mapped to roles without the/linear/statescatalog by seeding the name→UUID map fromconfig.linear.states.New Features
fleet factory canary <issue>: runs a real dry-run triage and exits non-zero if the issue isn’t dispatch-ready, returning the skip reason. Use this in CI/cron to detect sync-fidelity drift.Written for commit eb3a1e8. Summary will update on new commits.