Skip to content

[Bug]: DSH integration refuses to apply when llm-pi-ai.providers is empty ("cannot patch without risking unrelated comments or formatting") #4260

Description

@a1980881039

Client or integration

OpenCodex dashboard

Area

Other

Summary

Enabling the DSH integration from the dashboard never applies. The panel shows this refusal, and the file is left untouched:

无法安全地更改配置: C:\Users\<user>\.dsh\settings.yaml uses YAML source opencodex cannot patch without risking unrelated comments or formatting, so it was left alone

The CLI agrees, and 恢复中心 stays empty, so nothing was written and no snapshot exists:

clientId: dsh
state: absent
installed: true
configPath: $DSH_HOME\settings.yaml
snapshotCount: 0
retentionDegraded: false

The trigger is an empty llm-pi-ai.providers container. The source-preserving patcher in src/integrations/omp-yaml-source.ts requires every segment of the path ["llm-pi-ai", "providers", "opencodex"] to already be a non-empty plain block map. When llm-pi-ai.providers exists but is empty, locatePath returns null instead of a missing-leaf insertion point, patchYamlFragmentSource returns null, and src/integrations/writer.ts turns that into the "left alone" refusal.

What I expected instead: an empty container should be an ordinary insertion case, exactly like an absent llm-pi-ai key already is. Both are states a user reaches without hand-editing YAML.

Reproduction

Minimal $DSH_HOME/settings.yaml:

llm-pi-ai:
  providers: {}
ui-theme:
  preference: system
  1. ocx start
  2. Open the dashboard, select DSH on the integration page, and toggle it on.
  3. Observe the refusal box. Nothing is written, and 恢复中心 registers no entry.
  4. ocx integration client status --client dsh reports state: absent and snapshotCount: 0.

I also drove the shipped patcher directly, calling patchYamlFragmentSource from src/integrations/omp-yaml-source.ts with the real settings file, the path above, and a small generated provider block. Five cases:

A) providers: {}                 -> null (refused)
B) providers:                    -> null (refused)   (empty block key; parses as null)
C) providers: with one sibling   -> patched OK
D) providers: with opencodex     -> patched OK
E) llm-pi-ai key absent entirely -> patched OK

Case A fails because isPlainBlockKey does not accept the providers: {} line, while readPath still finds the key defined, so locatePath returns null rather than falling through to the missing-key branch. Case B reaches the isPlainRecord(readPath(parsed, prefix)) guard, where an empty block key parses as null and the guard rejects it.

The empty shape is not exotic. It is what the file holds once the last provider entry is removed, and an integration that only applies when an unrelated provider already exists cannot be enabled on a clean profile.

Version

2.50.0

Operating system

Windows 11 Pro (10.0.26200)

Provider and model

Not provider-specific. Reproduced with no providers configured at all.

Logs or error output

PS> ocx integration client status --client dsh
clientId: dsh
state: absent
installed: true
configPath: $DSH_HOME\settings.yaml
snapshotCount: 0
retentionDegraded: false

Dashboard refusal: 无法安全地更改配置: $DSH_HOME\settings.yaml uses YAML source opencodex cannot patch without risking unrelated comments or formatting, so it was left alone

Redacted configuration

llm-pi-ai:
  providers: {}

Suggested fix

Let locatePath treat an empty container as a missing leaf:

  • accept a plain block key whose value is an empty inline collection (providers: {}, providers: []) as a match, and
  • allow readPath(parsed, prefix) to be null or an empty record when the matched source line is a plain key with no block children, then insert the child at parentIndent + 2.

upsertSource already has the insertion branch for this case, so the change stays confined to how the path is located.

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions