Skip to content

fix(model): parse model specs with multiple slashes and colons correctly#930

Open
decode2 wants to merge 3 commits into
Gentleman-Programming:mainfrom
decode2:fix/model-spec-parsing-issue-802
Open

fix(model): parse model specs with multiple slashes and colons correctly#930
decode2 wants to merge 3 commits into
Gentleman-Programming:mainfrom
decode2:fix/model-spec-parsing-issue-802

Conversation

@decode2

@decode2 decode2 commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes model spec parsing in \ReadCurrentModelAssignments\ to correctly handle specs with multiple slashes and colons like \openrouter/qwen/qwen3.6-plus:free.

Problem

The read-path parser was splitting on the first colon before checking for slashes. This broke model specs where the provider is before the first slash, not before the colon.

For example, \openrouter/qwen/qwen3.6-plus:free\ was incorrectly parsed as:

  • ProviderID: \openrouter/qwen/qwen3.6-plus\
  • ModelID: \ ree\

When it should be:

  • ProviderID: \openrouter\
  • ModelID: \qwen/qwen3.6-plus:free\

Solution

Changed the parsing logic to find the first separator (either /\ or :) instead of preferring colon-first. This aligns with the write-path parser in \parseModelSpec\ (sync.go) which already handles this correctly.

Changes

  • Modified \internal/components/sdd/read_assignments.go\ to iterate through the string and find the first /\ or :\ character
  • Removed unused \strings\ import
  • Added test \TestReadCurrentModelAssignmentsOpenRouterFreeModel\ to verify OpenRouter free-model specs are parsed correctly

Testing

All existing tests pass, including:

  • Colon-separated specs: \�nthropic:claude-sonnet-4-20250514\
  • Slash-separated specs: \zai-coding-plan/glm-5-turbo\
  • Mixed separators in the same file
  • New test for OpenRouter free-model specs: \openrouter/qwen/qwen3.6-plus:free\

Fixes #802

Summary by CodeRabbit

  • Bug Fixes
    • Improved parsing to split at the first separator (/ or :) for model strings with multiple separators.
    • Now skips malformed assignment/model entries with missing or leading separators instead of attempting to process them.
    • Enhanced support for OpenRouter-style “free” model names in profile and assignment detection.
  • Tests
    • Added unit tests covering OpenRouter free-model parsing for both model extraction and current assignment reading.

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Model-spec parsing now splits on the first / or : in both SDD assignment reading and profile extraction. Regression tests cover the OpenRouter free-model string in both paths.

Changes

SDD model spec first-separator fix

Layer / File(s) Summary
First-separator parsing in ReadCurrentModelAssignments
internal/components/sdd/read_assignments.go, internal/components/sdd/read_assignments_test.go
ReadCurrentModelAssignments now splits model specs at the first / or : separator, and a regression test covers openrouter/qwen/qwen3.6-plus:free.
First-separator parsing in extractModelFromAgent
internal/components/sdd/profiles.go, internal/components/sdd/profiles_test.go
extractModelFromAgent now splits on the first / or : separator, and tests cover the same OpenRouter free-model string through extractModelFromAgent and DetectProfiles.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR fixes the first-separator parsing in read_assignments.go and profiles.go, but it does not address the same bug in internal/cli/sync.go required by #802. Apply the same first-separator parsing fix to internal/cli/sync.go parseModelSpec, or narrow the issue scope if that path is intentionally excluded.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: fixing model-spec parsing with multiple separators.
Out of Scope Changes check ✅ Passed All code changes are directly tied to the model-spec parsing fix and its regression tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ 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.

@Alan-TheGentleman Alan-TheGentleman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The ReadCurrentModelAssignments fix looks right, but #802 also calls out the same parsing bug in internal/components/sdd/profiles.go. That path still parses colon-first, so openrouter/qwen/qwen3.6-plus:free gets split incorrectly for named SDD profiles. Please update the profile parsing path to use the same first-separator logic and add a regression test through DetectProfiles or extractModelFromAgent.

decode2 added a commit to decode2/gentle-ai that referenced this pull request Jun 20, 2026
Apply same fix as read_assignments.go to profiles.go: find the first
separator (either '/' or ':') instead of preferring colon-first.

This fixes parsing of specs like 'openrouter/qwen/qwen3.6-plus:free'
where the provider is before the first slash, not before the colon.

Added test TestExtractModelFromAgent_OpenRouterFreeModel to verify
the fix works correctly for named SDD profiles.

Addresses review feedback from Alan-TheGentleman on PR Gentleman-Programming#930
@decode2

decode2 commented Jun 20, 2026

Copy link
Copy Markdown
Contributor Author

@Alan-TheGentleman Applied the same fix to profiles.go as requested.

The extractModelFromAgent function now finds the first separator (either '/' or ':') instead of preferring colon-first, matching the fix in read_assignments.go.

Added test TestExtractModelFromAgent_OpenRouterFreeModel to verify that 'openrouter/qwen/qwen3.6-plus:free' is parsed correctly for named SDD profiles.

Both parsing paths now handle multi-slash/colon specs consistently.

@decode2

decode2 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Hi @Alan-TheGentleman, the changes are complete:

  • Updated both parsing paths (\ReadCurrentModelAssignments\ and \�xtractModelFromAgent/\DetectProfiles) to scan for the first /\ or :\ character left-to-right rather than preferring colon-first.
  • Added \TestDetectProfiles_OpenRouterFreeModel\ regression coverage to assert that multi-slash/colon OpenRouter free model specs parse correctly for named SDD profiles.

Ready for re-review!

@Alan-TheGentleman Alan-TheGentleman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The model parsing fix itself looks right, and I added the missing type:bug label. I am still requesting changes because this branch includes an unrelated GGA backup test commit from another PR. Please rebase or cherry-pick so this PR only contains the #802 model parsing changes, then ping for re-review.

decode2 added a commit to decode2/gentle-ai that referenced this pull request Jun 26, 2026
Apply same fix as read_assignments.go to profiles.go: find the first
separator (either '/' or ':') instead of preferring colon-first.

This fixes parsing of specs like 'openrouter/qwen/qwen3.6-plus:free'
where the provider is before the first slash, not before the colon.

Added test TestExtractModelFromAgent_OpenRouterFreeModel to verify
the fix works correctly for named SDD profiles.

Addresses review feedback from Alan-TheGentleman on PR Gentleman-Programming#930
@decode2 decode2 force-pushed the fix/model-spec-parsing-issue-802 branch from f7bc6c3 to eb4e044 Compare June 26, 2026 14:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/components/sdd/profiles.go`:
- Around line 417-470: cleanupStaleProfileJDAgents is writing user settings
directly with filemerge.WriteFileAtomic, which bypasses the required
backup/restore flow for config mutations. Update this cleanup path to go through
the same backup-enabled writer used for settings sync (the shared config
write/merge path in profiles.go), and keep the stale-key removal logic inside
that flow so any failure can be recovered consistently.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0978578b-4be9-40eb-a6a8-9b0cd87c23f2

📥 Commits

Reviewing files that changed from the base of the PR and between f7bc6c3 and eb4e044.

📒 Files selected for processing (4)
  • internal/components/sdd/profiles.go
  • internal/components/sdd/profiles_test.go
  • internal/components/sdd/read_assignments.go
  • internal/components/sdd/read_assignments_test.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/components/sdd/profiles.go`:
- Around line 417-470: cleanupStaleProfileJDAgents is writing user settings
directly with filemerge.WriteFileAtomic, which bypasses the required
backup/restore flow for config mutations. Update this cleanup path to go through
the same backup-enabled writer used for settings sync (the shared config
write/merge path in profiles.go), and keep the stale-key removal logic inside
that flow so any failure can be recovered consistently.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0978578b-4be9-40eb-a6a8-9b0cd87c23f2

📥 Commits

Reviewing files that changed from the base of the PR and between f7bc6c3 and eb4e044.

📒 Files selected for processing (4)
  • internal/components/sdd/profiles.go
  • internal/components/sdd/profiles_test.go
  • internal/components/sdd/read_assignments.go
  • internal/components/sdd/read_assignments_test.go
🛑 Comments failed to post (1)
internal/components/sdd/profiles.go (1)

417-470: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Route stale-JD cleanup through the backup-enabled config writer.

This helper rewrites settingsPath directly via filemerge.WriteFileAtomic, so the new cleanup path bypasses the backup/restore guarantee required for user config writes. If this cleanup removes the wrong keys or is interrupted mid-sync, there is no visible rollback path here.

As per path instructions, internal/components/**/*.go: Any write to user config must have a backup/restore path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/components/sdd/profiles.go` around lines 417 - 470,
cleanupStaleProfileJDAgents is writing user settings directly with
filemerge.WriteFileAtomic, which bypasses the required backup/restore flow for
config mutations. Update this cleanup path to go through the same backup-enabled
writer used for settings sync (the shared config write/merge path in
profiles.go), and keep the stale-key removal logic inside that flow so any
failure can be recovered consistently.

Source: Path instructions

@decode2

decode2 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

@Alan-TheGentleman rebase complete! I've clean-rebased the branch and dropped the unrelated GGA backup test commit as requested. Ready for re-review.

@decode2

decode2 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Re: CodeRabbit comment on cleanupStaleProfileJDAgents (lines 417-470)

Verified the finding against the current code — this is a false positive from the path instruction.

WriteFileAtomic is already the backup/restore mechanism this codebase uses for all user config writes. It writes to a temp file in the same directory and renames atomically, so there is no partial-write failure mode. This is the same pattern used by:

  • mergeJSONFile (line 1452 in inject.go) — the shared config write/merge path referenced in the CR comment itself
  • RemoveProfileAgents (line 766 in profiles.go) — another user config mutation in the same file

Routing cleanupStaleProfileJDAgents through mergeJSONFile would be incorrect: cleanup is a key-deletion pass, not a merge of an overlay. Using the merge path would require synthesizing a fake overlay, which would be semantically wrong and harder to read.

The function is consistent with the rest of the codebase. No change needed.

@decode2

decode2 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

@Alan-TheGentleman ready for re-review! The branch is clean after the rebase, CI is fully green, and I've responded inline to the remaining CodeRabbit comment explaining why no code change is needed there.

decode2 added 3 commits July 1, 2026 00:17
The read-path parser in ReadCurrentModelAssignments was splitting on
the first colon before checking for slashes. This broke model specs
like 'openrouter/qwen/qwen3.6-plus:free' where the provider is before
the first slash, not before the colon.

Changed the parsing logic to find the first separator (either '/' or
':') instead of preferring colon-first. This aligns with the write-path
parser in parseModelSpec (sync.go) which already handles this correctly.

Added test coverage for OpenRouter free-model specs with the format
'provider/org/model:variant'.

Fixes Gentleman-Programming#802
Apply same fix as read_assignments.go to profiles.go: find the first
separator (either '/' or ':') instead of preferring colon-first.

This fixes parsing of specs like 'openrouter/qwen/qwen3.6-plus:free'
where the provider is before the first slash, not before the colon.

Added test TestExtractModelFromAgent_OpenRouterFreeModel to verify
the fix works correctly for named SDD profiles.

Addresses review feedback from Alan-TheGentleman on PR Gentleman-Programming#930
@decode2 decode2 force-pushed the fix/model-spec-parsing-issue-802 branch from eb4e044 to 923e4a9 Compare July 1, 2026 03:18

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/components/sdd/read_assignments.go`:
- Around line 81-97: The first-separator parsing is correct, but the same
slash/colon scan is duplicated in `read_assignments.go`, `extractModelFromAgent`
in `profiles.go`, and the remaining parser in `sync.go`, which risks future
drift. Extract the shared separator logic into a single helper such as
`splitModelSpec(modelStr string) (providerID, modelID string, ok bool)` in a
common package, then update all three call sites to use it and keep the
empty-provider/model validation in one place.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: eb1aa6d5-eff5-49f5-aa82-629a3a6403bc

📥 Commits

Reviewing files that changed from the base of the PR and between eb4e044 and 923e4a9.

📒 Files selected for processing (4)
  • internal/components/sdd/profiles.go
  • internal/components/sdd/profiles_test.go
  • internal/components/sdd/read_assignments.go
  • internal/components/sdd/read_assignments_test.go

Comment on lines +81 to +97
// Find the first separator (either '/' or ':') to correctly parse
// model specs like "openrouter/qwen/qwen3.6-plus:free" where the
// provider is before the first slash, not before the colon.
// Issue #802: colon-first parsing broke OpenRouter free-model specs.
sep := -1
for i, c := range modelStr {
if c == '/' || c == ':' {
sep = i
break
}
}
if idx <= 0 {
if sep <= 0 {
// No separator or separator is the first character — skip malformed value.
continue
}
providerID := modelStr[:idx]
modelID := modelStr[idx+1:]
providerID := modelStr[:sep]
modelID := modelStr[sep+1:]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Correct fix, but consider extracting the shared separator-scan into one helper.

The first-separator logic is correct (boundary checks for empty provider/model are sound). However, this exact scan is duplicated almost verbatim in extractModelFromAgent (internal/components/sdd/profiles.go, lines 240-247), and the linked issue notes a third parsing site in internal/cli/sync.go still needs the same fix. Having independent copies is precisely how the original bug (colon-first vs slash-first drift) was introduced. Extracting a single splitModelSpec(modelStr string) (providerID, modelID string, ok bool) helper (e.g., in the model package) would let all three call sites share one implementation and prevent future divergence.

♻️ Suggested shared helper
// in internal/model (or a shared sdd util package)
func SplitModelSpec(modelStr string) (providerID, modelID string, ok bool) {
	sep := -1
	for i := 0; i < len(modelStr); i++ {
		if modelStr[i] == '/' || modelStr[i] == ':' {
			sep = i
			break
		}
	}
	if sep <= 0 {
		return "", "", false
	}
	modelID = modelStr[sep+1:]
	if modelID == "" {
		return "", "", false
	}
	return modelStr[:sep], modelID, true
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/components/sdd/read_assignments.go` around lines 81 - 97, The
first-separator parsing is correct, but the same slash/colon scan is duplicated
in `read_assignments.go`, `extractModelFromAgent` in `profiles.go`, and the
remaining parser in `sync.go`, which risks future drift. Extract the shared
separator logic into a single helper such as `splitModelSpec(modelStr string)
(providerID, modelID string, ok bool)` in a common package, then update all
three call sites to use it and keep the empty-provider/model validation in one
place.

@decode2

decode2 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

@Alan-TheGentleman thanks for the review — both points are addressed and the branch is freshly rebased on main:

  1. Profile parsing path (internal/components/sdd/profiles.go): extractModelFromAgent now splits on the first separator (/ or :) instead of colon-first, so openrouter/qwen/qwen3.6-plus:free resolves to provider openrouter / model qwen/qwen3.6-plus:free. Regression coverage added at both layers you called out — TestExtractModelFromAgent_OpenRouterFreeModel (direct) and TestDetectProfiles_OpenRouterFreeModel (end-to-end through DetectProfiles, asserting OrchestratorModel and PhaseAssignments["sdd-apply"]).
  2. Unrelated GGA backup commit: rebased out — the branch now contains only the three fix(model): provider/model specs split at first colon even when a slash comes first #802 parsing commits.

Scope stays strictly on the #802 colon-vs-slash bug (read + profile paths); go test ./internal/components/sdd/... is green. Ready for re-review whenever you have a moment 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(model): provider/model specs split at first colon even when a slash comes first

2 participants