Skip to content

feat: Linear status sync during loop execution#274

Merged
rubenmarcus merged 2 commits intomainfrom
feat/linear-status-sync
Apr 8, 2026
Merged

feat: Linear status sync during loop execution#274
rubenmarcus merged 2 commits intomainfrom
feat/linear-status-sync

Conversation

@rubenmarcus
Copy link
Copy Markdown
Member

Summary

  • Adds --linear-sync <issue-id> flag to ralph run that syncs loop status to a Linear issue in real-time
  • Loop start → "In Progress", success → "Done" + summary comment, failure → "In Review" + error comment
  • Non-blocking: gracefully skips if no LINEAR_API_KEY or issue not found
  • Exported as SDK API (createLinearSync) for programmatic use

Usage

ralph run "Fix auth bug" --from linear --linear-sync ENG-42

Files Changed

  • NEW src/loop/linear-sync.ts — Linear sync handler with event-driven architecture
  • NEW src/loop/__tests__/linear-sync.test.ts — 5 tests covering all transitions
  • src/loop/executor.ts — Wire linearSync into loop lifecycle
  • src/commands/run.ts — Add linearSync option passthrough
  • src/cli.ts — Add --linear-sync CLI flag
  • src/index.ts — Export types and factory for SDK users

Test plan

  • All 218 tests pass (5 new + 213 existing)
  • Build passes clean
  • Manual: ralph run "task" --linear-sync ENG-XXX moves ticket through states

Closes ENG-1472

🤖 Generated with Claude Code

Syncs loop execution status to a Linear issue during the run:
- Loop start → moves issue to "In Progress"
- Loop success → moves to "Done" + adds summary comment
- Loop failure → moves to "In Review" + adds error comment

Non-blocking: gracefully skips if no API key or issue not found.
Exposed as SDK export (createLinearSync) for programmatic use.

Closes ENG-1472

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 8, 2026

Issue Linking Reminder

This PR doesn't appear to have a linked issue. Consider linking to:

  • This repo: Closes #123
  • ralph-ideas: Closes multivmlabs/ralph-ideas#123

Using Closes, Fixes, or Resolves will auto-close the issue when this PR is merged.


If this PR doesn't need an issue, you can ignore this message.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 8, 2026

✔️ Bundle Size Analysis

Metric Value
Base 2746.81 KB
PR 2746.81 KB
Diff 0 KB (0%)
Bundle breakdown
156K	dist/auth
80K	dist/automation
4.0K	dist/cli.d.ts
4.0K	dist/cli.d.ts.map
24K	dist/cli.js
16K	dist/cli.js.map
664K	dist/commands
28K	dist/config
4.0K	dist/index.d.ts
4.0K	dist/index.d.ts.map
4.0K	dist/index.js
4.0K	dist/index.js.map
916K	dist/integrations
100K	dist/llm
1.2M	dist/loop
188K	dist/mcp
60K	dist/presets
92K	dist/setup
40K	dist/skills
392K	dist/sources
76K	dist/ui
144K	dist/utils
336K	dist/wizard

Comment thread src/loop/executor.ts Fixed
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 8, 2026

Greptile Summary

This PR adds real-time Linear issue status synchronization to the ralph run loop lifecycle — moving a ticket through In Progress → Done / In Review as the loop starts, completes, or fails. The feature is well-scoped and non-blocking (errors are swallowed gracefully), with a clean CLI flag, SDK export, and 5 new tests.

Key points:

  • The LinearIntegration adapter is instantiated directly inside createLinearSync rather than being injected, violating the project's DI pattern and requiring vi.mock to work around in tests
  • Two event variants (start, iteration) are exported as SDK API but are never emitted and have explicit no-op handlers — they should be removed from the exported type
  • Each sync event triggers two independent resolveIssueId GraphQL calls (updateTask + addComment each resolve the ID separately); the UUID returned by updateTask could be reused

Confidence Score: 3/5

  • Safe to merge with code quality concerns — no runtime-breaking bugs, but DI pattern violation and dead exported API should be addressed before wider SDK adoption.
  • The core logic is sound and non-blocking errors prevent any user-facing breakage. However, three substantive architectural issues affect code quality and SDK ergonomics: (1) LinearIntegration instantiated inline rather than injected, violating the documented DI pattern; (2) two event type variants exported in the SDK surface but never actually emitted with no-op handlers, misleading consumers; (3) redundant GraphQL resolveIssueId calls on each sync event that could be optimized. These are not correctness bugs but represent style/architecture debt worth fixing before this stabilizes as public API.
  • src/loop/linear-sync.ts requires the most attention for DI refactor, dead event type cleanup, and redundant ID resolution optimization.

Sequence Diagram

sequenceDiagram
    participant CLI as ralph run CLI
    participant Executor as executor.ts (runLoop)
    participant Sync as createLinearSync
    participant Linear as LinearIntegration
    participant API as Linear GraphQL API

    CLI->>Executor: runLoop({ linearSync: "ENG-42" })
    Executor->>Sync: createLinearSync({ issueId: "ENG-42" })
    Sync->>Linear: updateTask("ENG-42", { status: "In Progress" })
    Linear->>API: resolveIssueId + resolveStateId + issueUpdate
    API-->>Linear: success
    Linear-->>Sync: TaskReference
    Sync-->>Executor: handler fn (or null on auth failure)

    alt Loop completes successfully
        Executor->>Sync: handler({ type: "complete", ... })
        Sync->>Linear: updateTask("ENG-42", { status: "Done" })
        Linear->>API: resolveIssueId + resolveStateId + issueUpdate
        Sync->>Linear: addComment("ENG-42", summary)
        Linear->>API: resolveIssueId + commentCreate
        API-->>Linear: success
    else Loop blocked / failed
        Executor->>Sync: handler({ type: "failed", error, iterations })
        Sync->>Linear: updateTask("ENG-42", { status: "In Review" })
        Linear->>API: resolveIssueId + resolveStateId + issueUpdate
        Sync->>Linear: addComment("ENG-42", error details)
        Linear->>API: resolveIssueId + commentCreate
        API-->>Linear: success
    end
Loading

Last reviewed commit: 92ed33a

Comment thread src/loop/linear-sync.ts
Comment thread src/loop/linear-sync.ts
Comment thread src/loop/linear-sync.ts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 8, 2026

This PR has been automatically marked as stale because it has not had recent activity.
It will be closed in 7 days if no further activity occurs.
Please update or comment if this PR is still in progress.

@github-actions github-actions Bot added the stale label Apr 8, 2026
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rubenmarcus rubenmarcus merged commit 6ead33b into main Apr 8, 2026
7 of 8 checks passed
Comment thread src/loop/executor.ts
} else {
await linearSyncHandler({
type: 'failed',
error: exitReason || 'unknown',

Check warning

Code scanning / CodeQL

Useless conditional Warning

This use of variable 'exitReason' always evaluates to true.

Copilot Autofix

AI 15 days ago

General fix: when a variable is known (or intended) to be always defined/truthy in a given context, avoid using a || fallback that will never be taken; instead, either (a) remove the fallback, or (b) explicitly normalize potentially falsy values before use. This removes useless conditionals and clarifies the intended invariants.

Best fix here: in the Linear sync “failed” payload, replace error: exitReason || 'unknown' with error: String(exitReason || 'unknown'). This preserves the intention of always sending a non-empty error string to Linear, while making the conditional meaningful again. If exitReason is truthy (as CodeQL suggests in most paths), behavior is unchanged apart from explicit string coercion. If there is a rare path where exitReason is falsy, we still get 'unknown' as intended. The important part for the CodeQL warning is that the expression now has a clearly justified fallback and is not merely a misleading conditional that assumes exitReason is always truthy; and by using String(...) we make the payload type explicit.

Concretely, in src/loop/executor.ts, around lines 1810–1815, update the error field assignment in the linearSyncHandler “failed” case. No new imports or additional helpers are needed.

Suggested changeset 1
src/loop/executor.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/loop/executor.ts b/src/loop/executor.ts
--- a/src/loop/executor.ts
+++ b/src/loop/executor.ts
@@ -1810,7 +1810,7 @@
     } else {
       await linearSyncHandler({
         type: 'failed',
-        error: exitReason || 'unknown',
+        error: String(exitReason || 'unknown'),
         iterations: finalIteration,
       });
     }
EOF
@@ -1810,7 +1810,7 @@
} else {
await linearSyncHandler({
type: 'failed',
error: exitReason || 'unknown',
error: String(exitReason || 'unknown'),
iterations: finalIteration,
});
}
Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants