Skip to content

fix: allow per-node backend selection alongside global --backend flag#74

Merged
clintecker merged 4 commits intomainfrom
fix/per-node-backend-selection-70
Apr 15, 2026
Merged

fix: allow per-node backend selection alongside global --backend flag#74
clintecker merged 4 commits intomainfrom
fix/per-node-backend-selection-70

Conversation

@clintecker
Copy link
Copy Markdown
Collaborator

@clintecker clintecker commented Apr 15, 2026

Summary

  • Root cause: registerCodergenHandler in registry.go only registered the full CodergenHandler when llmClient != nil or defaultBackend was claude-code/acp. This meant pipelines with per-node backend: claude-code attrs (but no global --backend flag) didn't get the handler registered. Additionally, the comment on selectBackend incorrectly stated that --backend claude-code routes ALL nodes through the claude CLI — in reality, the per-node attr always wins (the code was already correct, just undocumented).
  • Fix:
    1. registerCodergenHandler now detects graphs with per-node backend attrs via graphHasPerNodeBackend() and registers the full handler in that case
    2. The stub (codergenFunc) now explicitly takes priority (early return), cleaning up the logic
    3. ensureNativeBackend gives a clear actionable error when a backend: native node has no API keys alongside --backend claude-code
    4. selectBackend comment documents the 3-level priority: per-node attr > global flag > native default

Test plan

  • TestSelectBackendNodeAttrWinsOverGlobalClaudeCodebackend: native attr overrides global --backend claude-code
  • TestSelectBackendNodeAttrClaudeCodeOverGlobalNativebackend: claude-code attr works when global default is native
  • TestSelectBackendNativeAttrWithGlobalClaudeCodeNoClient — clear error when backend: native + no API keys + global claude-code
  • TestSelectBackendGlobalClaudeCodeUsedWhenNoNodeAttr — global --backend claude-code still applies when no per-node attr
  • TestGraphHasPerNodeBackend — registry detection of mixed-backend graphs (4 subtests)
  • All 15 packages pass with race detector and coverage checks

Closes #70

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for mixed-backend pipelines, allowing different nodes to use different backends within the same pipeline.
  • Bug Fixes

    • Fixed backend selection precedence: per-node backend attributes now correctly override global CLI flags.
    • Improved error messages with actionable guidance when required authentication is missing for a selected backend.

Per-node 'backend' attribute now always wins over the global --backend
CLI flag, enabling mixed-backend pipelines (issue #70). A node with
'backend: native' uses the native LLM client even when --backend
claude-code is set globally.

Changes:
- selectBackend: document 3-level priority (per-node > global > default)
- registerCodergenHandler: register CodergenHandler when graph has
  per-node backend attrs, even if global default is native with no
  --backend flag set
- ensureNativeBackend: surface a clear, actionable error when a
  'backend: native' node has no API keys configured alongside
  --backend claude-code
- New tests: TestSelectBackendNodeAttrWinsOverGlobalClaudeCode,
  TestSelectBackendNodeAttrClaudeCodeOverGlobalNative,
  TestSelectBackendNativeAttrWithGlobalClaudeCodeNoClient,
  TestSelectBackendGlobalClaudeCodeUsedWhenNoNodeAttr,
  TestGraphHasPerNodeBackend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 15, 2026

Warning

Rate limit exceeded

@clintecker has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 15 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 9 minutes and 15 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7aa72343-275e-4b24-97dd-c572b73206fe

📥 Commits

Reviewing files that changed from the base of the PR and between f50052e and 0b2b3f0.

📒 Files selected for processing (3)
  • .gitignore
  • pipeline/handlers/codergen.go
  • pipeline/handlers/registry.go

Walkthrough

The pull request implements per-node backend attribute support, allowing individual graph nodes to override the global --backend flag. This enables mixed-backend pipelines where nodes can explicitly select between native and claude-code backends with a defined precedence: per-node attribute > global flag > default native.

Changes

Cohort / File(s) Summary
Documentation
CHANGELOG.md, CLAUDE.md
Updated documentation to reflect new backend selection precedence rules and clarify that per-node backend attributes now override global CLI flags, with nodes explicitly set to backend: native using the native backend even when the global flag is claude-code.
Backend Selection Logic
pipeline/handlers/codergen.go
Modified selectBackend to treat per-node backend attribute as highest priority, falling back to global defaultBackendName and finally native. Enhanced ensureNativeBackend error messaging to provide actionable context when native backend is selected but required API keys are missing.
Handler Registration
pipeline/handlers/registry.go
Restructured registerCodergenHandler to check the codergen stub first, then expanded full CodergenHandler registration conditions to include graphs with per-node backend attributes via new graphHasPerNodeBackend function that scans for nodes with non-empty "backend" attributes.
Test Coverage
pipeline/handlers/backend_claudecode_test.go
Added comprehensive test cases validating per-node backend precedence over global defaults, error message content when native backend lacks API keys, handling of nodes without backend attributes, and graphHasPerNodeBackend behavior across various graph configurations.

Sequence Diagram(s)

sequenceDiagram
    participant Node as Node in Graph
    participant SelectBackend as selectBackend()
    participant Registry as Handler Registry
    participant BackendInstance as Backend Instance<br/>(native or claude-code)

    Node->>SelectBackend: Request backend selection
    alt Per-node 'backend' attribute exists
        SelectBackend->>SelectBackend: Use per-node value
    else Per-node attribute missing
        SelectBackend->>SelectBackend: Check global defaultBackendName
        alt Global flag set
            SelectBackend->>SelectBackend: Use global flag
        else Global flag not set
            SelectBackend->>SelectBackend: Default to native
        end
    end
    SelectBackend->>Registry: Report selected backend
    
    alt graphHasPerNodeBackend detected
        Registry->>Registry: Register full CodergenHandler
    else No per-node backends
        Registry->>Registry: Use existing registration logic
    end
    
    Registry->>BackendInstance: Initialize selected backend
    alt backend == 'native'
        BackendInstance->>BackendInstance: Initialize native LLM client
    else backend == 'claude-code'
        BackendInstance->>BackendInstance: Initialize claude CLI backend
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 Hop, hop, backends now blend,
Per-node choices, no more contend!
Native or claude-code, pick with care,
Mixed pipelines flourish everywhere!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: enabling per-node backend selection to work alongside a global --backend flag, which is the core fix implemented across all modified files.
Linked Issues check ✅ Passed The PR fully addresses issue #70's requirement to support mixed-backend pipelines by implementing per-node backend selection with proper priority handling over global flags, enabling nodes to route to different backends (claude-code vs native).
Out of Scope Changes check ✅ Passed All changes are directly scoped to enabling per-node backend selection: documentation updates, test coverage for backend selection precedence, handler registration logic for mixed-backend graphs, and backend selection priority. No unrelated changes detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/per-node-backend-selection-70

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
pipeline/handlers/codergen.go (1)

170-176: Tighten native-error wording to include the codergen alias.

On Line 172, the message says backend: native, but this branch is also reachable via backend: codergen. Consider mentioning both to avoid user confusion.

✏️ Suggested wording tweak
-				"node requests native backend via \"backend: native\" attr, but no API keys are configured — "+
+				"node requests native backend via \"backend: native|codergen\" attr, but no API keys are configured — "+
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pipeline/handlers/codergen.go` around lines 170 - 176, Update the error
message returned in the branch that checks h.defaultBackendName (the clause
where defaultBackendName == "claude-code" || defaultBackendName == "acp") to
mention both "backend: native" and the alias "backend: codergen" so users know
this branch is reachable via either attribute; modify the fmt.Errorf call in
codergen.go to include both labels (e.g., '"backend: native" (alias "backend:
codergen")') while keeping the existing guidance about configuring LLM provider
API keys and the --backend %s insertion using h.defaultBackendName.
pipeline/handlers/registry.go (1)

216-223: Consider adding a nil-graph guard in graphHasPerNodeBackend for defensive safety.

Line 217 dereferences graph.Nodes directly. While the current code flow prevents nil from reaching this function, a defensive check prevents accidental panics if the function is called with nil in the future.

🛡️ Defensive fix
 func graphHasPerNodeBackend(graph *pipeline.Graph) bool {
+	if graph == nil {
+		return false
+	}
 	for _, n := range graph.Nodes {
 		if n.Attrs["backend"] != "" {
 			return true
 		}
 	}
 	return false
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pipeline/handlers/registry.go` around lines 216 - 223, Add a nil-guard at the
start of graphHasPerNodeBackend: check if the incoming *pipeline.Graph (graph)
is nil and return false immediately to avoid dereferencing graph.Nodes; keep the
existing loop and logic unchanged (the range over graph.Nodes is safe if graph
is non-nil). This change should be applied inside the graphHasPerNodeBackend
function to prevent panics when a nil graph is passed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pipeline/handlers/codergen.go`:
- Around line 170-176: Update the error message returned in the branch that
checks h.defaultBackendName (the clause where defaultBackendName ==
"claude-code" || defaultBackendName == "acp") to mention both "backend: native"
and the alias "backend: codergen" so users know this branch is reachable via
either attribute; modify the fmt.Errorf call in codergen.go to include both
labels (e.g., '"backend: native" (alias "backend: codergen")') while keeping the
existing guidance about configuring LLM provider API keys and the --backend %s
insertion using h.defaultBackendName.

In `@pipeline/handlers/registry.go`:
- Around line 216-223: Add a nil-guard at the start of graphHasPerNodeBackend:
check if the incoming *pipeline.Graph (graph) is nil and return false
immediately to avoid dereferencing graph.Nodes; keep the existing loop and logic
unchanged (the range over graph.Nodes is safe if graph is non-nil). This change
should be applied inside the graphHasPerNodeBackend function to prevent panics
when a nil graph is passed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e0759834-8a53-4b41-b27a-765cbbc18d64

📥 Commits

Reviewing files that changed from the base of the PR and between ad9ce91 and f50052e.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • CLAUDE.md
  • pipeline/handlers/backend_claudecode_test.go
  • pipeline/handlers/codergen.go
  • pipeline/handlers/registry.go

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes mixed-backend pipeline support by ensuring the codergen handler is registered when any node specifies a per-node backend attribute, even if no global --backend flag is set. It also clarifies and tests backend selection precedence and improves the error message when a node requires the native backend but no API keys/client are available.

Changes:

  • Register the full CodergenHandler when the graph contains any per-node backend attrs, and make the codergen stub override (codergenFunc) take priority.
  • Document backend selection precedence (per-node attr > global flag > native default) and improve the “native backend missing client” error when global backend is external.
  • Add tests covering backend precedence, missing-client error messaging, and registry detection of per-node backend usage.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
pipeline/handlers/registry.go Registers CodergenHandler for graphs with per-node backend attrs; adds graphHasPerNodeBackend helper; prioritizes stub handler.
pipeline/handlers/codergen.go Documents backend selection precedence; adds actionable error for backend: native when no client is available under external global backend.
pipeline/handlers/backend_claudecode_test.go Adds tests for backend precedence, error message content, and graphHasPerNodeBackend.
CLAUDE.md Updates documentation to reflect correct precedence of per-node backend over global flag.
CHANGELOG.md Adds an Unreleased “Fixed” entry describing the mixed-backend fix and improved errors.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@clintecker
Copy link
Copy Markdown
Collaborator Author

@copilot please address all the review feedback

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 15, 2026

@copilot please address all the review feedback

Addressed both CodeRabbit nitpicks in commit 4c1719e:

  1. codergen.go: Updated ensureNativeBackend error message to mention both "backend: native" and its alias "backend: codergen" so users know either attribute triggers this path.
  2. registry.go: Added nil-guard at the top of graphHasPerNodeBackend — returns false immediately if graph == nil to prevent any future panic.

All 15 packages pass with no regressions.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 114 to +118
// selectBackend chooses the appropriate AgentBackend based on node attributes
// and global default settings. When --backend claude-code is set, ALL nodes
// go through the claude CLI — non-Anthropic provider/model attrs are stripped
// so the CLI uses its default model under the user's subscription.
// and global default settings. Priority:
// 1. Per-node "backend" attribute (highest — always wins over global flag)
// 2. Global --backend flag (fallback for nodes without explicit backend attr)
// 3. Default: native
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

The updated header comment correctly documents that per-node "backend" overrides the global flag, but later in this function there’s still an inline comment saying the global --backend flag applies to all nodes. Please update/remove that inline comment so the function’s internal documentation is consistent with the stated priority.

Copilot uses AI. Check for mistakes.
Comment thread pipeline/handlers/registry.go Outdated
Comment on lines +214 to +215
// graphHasPerNodeBackend returns true if any node in the graph specifies an
// explicit "backend" attribute, indicating a mixed-backend pipeline.
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

The docstring says "explicit "backend" attribute", but the implementation only returns true when the value is non-empty (n.Attrs["backend"] != ""). Either adjust the comment to say "non-empty backend attr" or change the check to key existence if empty values should still be treated as explicitly set.

Suggested change
// graphHasPerNodeBackend returns true if any node in the graph specifies an
// explicit "backend" attribute, indicating a mixed-backend pipeline.
// graphHasPerNodeBackend returns true if any node in the graph specifies a
// non-empty "backend" attribute, indicating a mixed-backend pipeline.

Copilot uses AI. Check for mistakes.
- Remove stale inline comment in selectBackend claiming global flag
  applies to all nodes.
- graphHasPerNodeBackend docstring says 'non-empty backend attr' to
  match the actual check.

Refs #70

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@clintecker clintecker merged commit fd4cfc0 into main Apr 15, 2026
2 checks passed
@clintecker clintecker deleted the fix/per-node-backend-selection-70 branch April 15, 2026 19:58
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.

Impossible to use claude-code with other models

3 participants