Skip to content

fix(ai): getNodeConfig drops top-level keys and delivers arrays as IJson when profile is set - #2039

Open
nishant-k02 wants to merge 4 commits into
rocketride-org:developfrom
nishant-k02:fix/RR-1839-getnodeconfig-top-level-keys
Open

fix(ai): getNodeConfig drops top-level keys and delivers arrays as IJson when profile is set#2039
nishant-k02 wants to merge 4 commits into
rocketride-org:developfrom
nishant-k02:fix/RR-1839-getnodeconfig-top-level-keys

Conversation

@nishant-k02

@nishant-k02 nishant-k02 commented Aug 18, 2026

Copy link
Copy Markdown

Description

Fixes both defects from #1839.

Defect 1 — with an explicit "profile" key set, getNodeConfig only read connConfig[profile], silently dropping every key the caller wrote at the top level (e.g. an "apikey" sitting alongside "profile" instead of nested under the profile name). The sibling branch (no "profile" key) already overlays top-level keys on top of a nested profile-named block — the two branches disagreed about where configuration could live depending on whether "profile" happened to be set.

Defect 2 — array/object config values arrive from the engine as IJson, not list/dict, so a node that type-checks its own declared field (isinstance(value, list)) silently falls through to its default and reports success while ignoring what was configured.

Fix

  • Extracted the top-level/nested overlay into a shared overlay_top_level helper and used it in both branches, so a key written at the top level is honoured regardless of whether "profile" is set. The "profile" selector key itself is explicitly excluded so it can't leak into the resolved config.
  • getNodeConfig now returns IJson.toDict(config) instead of the raw config. Verified against the real rocketlib.types.IJson.toDict (recursive, no-op on already-native values) that this is safe to apply unconditionally at the return boundary rather than pushing the conversion onto every node.

Precedence, both branches now agree: profile defaults < nested profile block < real top-level values (top-level wins).

A note on #1979

I noticed #1979 (open, same files) after starting this PR. It fixes the same reported symptom but takes the opposite precedence — profile-specific (nested) keys win over top-level — and only touches the explicit-profile branch, not the default-profile branch. Since the default-profile branch's existing, currently-shipped, tested behavior already has top-level winning over nested (test_top_level_overrides_nested_value, test_real_top_level_beats_empty_nested_default in test_config_shapes.py), #1979's change would leave the two branches disagreeing again — just with the opposite branch now being the odd one out, which is the exact meta-problem #1839 raised ("the two branches disagree about where a user's configuration lives"). I chose to match the already-shipped precedent instead of the issue's literal suggested text, so the fix is a pure bug fix rather than a behavior change to already-shipped, tested resolution. Flagging this explicitly since it's a genuine design decision the issue author (Aryan-070) also called out as unresolved — happy to adjust if maintainers prefer #1979's direction, but in that case the default-profile branch would need the same precedence flip for consistency, which is a larger, more disruptive change to existing behavior.

Testing performed

  • TestExplicitProfileBranchTopLevelFields (4 tests, defect 1) — top-level fields resolve with an explicit profile, top-level overrides a nested value, a None top-level placeholder doesn't clobber nested, and "profile" itself doesn't leak into the resolved config.
  • TestNativeConfigTypes (3 tests, defect 2) — an IJson-wrapped array resolves to a native list, nested IJson-wrapped objects resolve recursively, and an already-native default value is unaffected. Upgraded the test file's IJson stub from a shallow dict-only shim to one that actually wraps a value and recurses, so these tests exercise real conversion behavior rather than a no-op.
  • Renamed TestExplicitProfileBranchUnaffectedTestExplicitProfileBranchNestedShape since its docstring/name asserted the branch was unaffected by config-shape handling, which is no longer accurate.
  • Full suite: 21/21 passed, including every pre-existing test (no regressions to the already-fixed default-profile branch or nested-shape resolution).
pytest packages/ai/tests/ai/common/test_config_shapes.py --noconftest -v
======================== 21 passed, 1 warning in 0.02s ========================

How I found this

Building an app against RocketRide Cloud, I configured an llm_anthropic node as:

{ "profile": "claude-sonnet-4-6", "apikey": "sk-ant-..." }

and got Invalid Anthropic API key format even though the key was valid — the apikey value was silently discarded. Nesting the same key under connConfig["claude-sonnet-4-6"] worked, which is exactly the shape described in #1839 defect 1.

Breaking changes

None for existing shapes. Flat config (no profile) and nested-under-profile config continue to resolve exactly as before; this adds resolution for a shape (top-level keys + explicit profile) that previously silently failed, and normalizes array/object return values to native types (previously-broken isinstance checks in nodes will now correctly see their configured values instead of silently falling back to defaults).

Fixes #1839

Summary by CodeRabbit

  • Bug Fixes

    • Improved configuration resolution for explicit and default profiles.
    • Top-level settings now correctly override profile values when provided.
    • Profile values are preserved when top-level settings are unset.
    • The profile selector is excluded from resolved configuration.
    • Configuration values, including nested arrays and objects, are consistently converted to native types.
  • Tests

    • Added coverage for nested resolution, precedence, selector exclusion, unset values, and recursive type conversion.

When connConfig has an explicit "profile" key, getNodeConfig only read
connConfig[profile], ignoring every key the caller wrote at the top
level (e.g. an "apikey" alongside "profile" instead of nested under
the profile name). The other branch (no "profile" key) already
overlays top-level keys on top of a nested profile-named block; the
two branches disagreed about where a caller's configuration could
live depending on whether "profile" happened to be set.

Extract that overlay into a shared helper (overlay_top_level) and use
it in both branches, so a key written at the top level is honoured
whether or not "profile" is set, and both branches agree on
precedence: profile defaults < nested profile block < real top-level
values. The "profile" selector key itself is now explicitly excluded
so it can't leak into the resolved config.

Confirmed independently against a real pipeline: an llm_anthropic
node configured with {"profile": "claude-sonnet-4-6", "apikey": "..."}
(flat) silently received no API key, while nesting the same apikey
under connConfig["claude-sonnet-4-6"] worked - reproducing exactly
what's described in rocketride-org#1839 defect 1.

Adds TestExplicitProfileBranchTopLevelFields covering the fix, and
renames the test class that pinned the old (dropped) behavior to
TestExplicitProfileBranchNestedShape since it no longer stands alone.

Fixes rocketride-org#1839
@github-actions

Copy link
Copy Markdown
Contributor
🤖 Internal: Discord sync marker

Auto-managed by the Discord notification workflow. Stores the linked Discord message ID and forum thread ID. Do not edit or delete.

@coderabbitai

coderabbitai Bot commented Aug 18, 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 36d0da37-8502-4165-a18f-c6dab276e922

📥 Commits

Reviewing files that changed from the base of the PR and between e0a2614 and 29641c0.

📒 Files selected for processing (1)
  • packages/ai/tests/ai/common/test_config_shapes.py

Included review availability: Your plan provides up to 8 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

getNodeConfig now shares overlay logic for explicit and default profiles. It preserves nested values when top-level values are None, excludes selector keys, and normalizes resolved IJson values. Tests cover precedence, recursive conversion, and native-list preservation.

Changes

Profile configuration resolution

Layer / File(s) Summary
Shared configuration overlay
packages/ai/src/ai/common/config.py
Adds overlay_top_level and routes both profile paths through it. The resolver merges profile defaults, nested values, and eligible top-level values, then applies IJson.toDict.
Configuration shape regression coverage
packages/ai/tests/ai/common/test_config_shapes.py
Adds tests for explicit-profile precedence, None handling, selector removal, recursive conversion of wrapped arrays and objects, and native-list preservation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 29641

This PR fixes configuration values being dropped or returned in unusable types; no actionable merge-blocking risk remains after normal checks and review.

Possibly related issues

  • rocketride-org/rocketride-server#1839: Covers the getNodeConfig top-level profile overlay and recursive IJson normalization changes.

Possibly related PRs

Suggested reviewers: jmaionchi, rod-christensen, stepmikhaylov

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies both main fixes: preserving top-level keys and converting array values returned by getNodeConfig when a profile is set.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% 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 unit tests (beta)
  • Create PR with unit tests

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.

Addresses rocketride-org#1839 defect 2: array/object config values arrive from the
engine as IJson, not list/dict, so a node that type-checks its own
declared field (isinstance(value, list)) silently falls through to
its default. IJson.toDict already does the recursive conversion and
is a no-op on values that are already native, so apply it once at the
return boundary rather than pushing the conversion onto every node,
per the issue's own suggested direction.

Adds TestNativeConfigTypes (3 tests) and upgrades the test file's
IJson stub from a shallow dict-only shim to one that actually wraps a
value and recurses, matching the real rocketlib.types.IJson.toDict
closely enough to exercise this. Also adds docstrings to the defect-1
tests to close out the docstring-coverage gap flagged in review.

Full suite: 21/21 passed (up from 18; +3 new).
@nishant-k02 nishant-k02 changed the title fix(ai): getNodeConfig drops top-level keys when profile is set fix(ai): getNodeConfig drops top-level keys and delivers arrays as IJson when profile is set Aug 18, 2026
Closes out the docstring-coverage check, which scores the whole file:
the pre-existing test methods (written before this PR) had none,
relying on their names alone. Every function in the file is now
documented; no behavior change.
Covers the four test classes (TestFlatShape, TestNestedShape,
TestMixedShapePrecedence, TestExplicitProfileBranchNestedShape) that
had per-method docstrings but no class-level summary of which
connConfig shape they exercise. File is now fully documented at both
the class and function level (35/35). No behavior change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

module:ai AI/ML modules

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(config): getNodeConfig drops top-level keys under a profile, and delivers arrays as IJson

1 participant