Skip to content

fix: channel editors silently dropping unknown fields on save#88

Merged
Iamlovingit merged 8 commits intoYuan-lab-LLM:mainfrom
naiqus:fix/openclaw-channel-editor-field-dropping
Apr 29, 2026
Merged

fix: channel editors silently dropping unknown fields on save#88
Iamlovingit merged 8 commits intoYuan-lab-LLM:mainfrom
naiqus:fix/openclaw-channel-editor-field-dropping

Conversation

@naiqus
Copy link
Copy Markdown
Contributor

@naiqus naiqus commented Apr 24, 2026

Problem

All four channel form editors in OpenClawConfigCenterPage.tsx (telegram, dingtalk-connector, slack, feishu) silently drop fields on Save.

User flow that loses data:

  1. Open a channel resource in the JSON tab, add any field not owned by the form (e.g. webhook, custom capabilities, additional feishu accounts).
  2. Switch to the Form tab and change anything (e.g. rotate a secret).
  3. Click Save.
  4. Reload the page. The field from step 1 is gone from the saved config.

Root cause — two lossy layers

Both the frontend editor and the backend storage normalizer rebuild each config from a hard-coded field allowlist instead of merging the patch onto the existing parsed config. Fixing only one of them is not enough to survive a reload.

Frontend: update*ChannelContentText in OpenClawConfigCenterPage.tsx

const existingConfig = parsed;
const config = {
  enabled: true,
  botToken: nextForm.botToken,
  dmPolicy: existingConfig.dmPolicy || "open",
  allowFrom: readStringArray(existingConfig.allowFrom) || ["*"],
};

Every key not in that literal is silently deleted. Feishu is the worst case — it collapses accounts to just { main: {...} }.

Backend: normalizeOpenClawResourceContent in openclaw_config_service.go

Called on every create/update; delegates to normalize{Telegram,Slack,DingTalk,Feishu}ChannelConfigForEnv. Those helpers serve double duty — they are correct for rendering the runtime env payload (appendCompiledOpenClawEnvPayload re-runs them at render time), but when applied verbatim as the storage normalizer they strip tenant-authored keys before persisting.

Concrete loss matrix (pre-fix)

Editor Preserved Silently dropped
telegram enabled, botToken, dmPolicy, allowFrom webhook, custom capabilities, any custom keys
dingtalk-connector enabled, clientId, clientSecret, allowFrom dmPolicy, webhook, custom keys
slack enabled, botToken, appToken, groupPolicy, channels, capabilities allowFrom, dmPolicy, custom keys
feishu enabled, accounts.main only every account other than main; every sibling field on accounts.main (e.g. verificationToken); every other top-level field

Fix

Frontend — spread existingConfig first, then override only form-owned keys so later literal keys win. Fallbacks (|| "open", || ["*"]) still evaluate against existingConfig. For feishu also deep-merge the accounts map and accounts.main so sibling accounts and sibling fields both survive.

Backend — introduce mergeOpenClawChannelConfigForStorage, which overlays the allowlist-normalized output onto the original parsed payload at storage time. Normalized keys still win; unknown keys pass through. For feishu the accounts map is merged member-wise.

Env-rendering semantics are unchanged: appendCompiledOpenClawEnvPayload continues to call the *ForEnv helpers at render time against the stored content, so the runtime env payload sees the same allowlist as before. Only storage is now a superset.

Diff

 backend/internal/services/openclaw_config_service.go      | 60 +++++++++-
 backend/internal/services/openclaw_config_service_test.go | 131 +++++++++++++---
 frontend/src/pages/openclaw/OpenClawConfigCenterPage.tsx  |  10 +
 3 files changed, 198 insertions(+), 3 deletions(-)

Verification

Backend tests

Two existing tests (TestResourcePayloadFromModelNormalizesStored{Channel,DingTalkChannel}JSON) asserted the pre-fix behavior (dropping legacyField); updated to assert it survives.

New regression guards:

  • TestNormalizeOpenClawResourceContentPreservesUnknownChannelFields — table-driven across telegram / dingtalk-connector / slack, asserts webhook, dmPolicy, allowFrom, custom capabilities all survive write-time normalization.
  • TestNormalizeOpenClawResourceContentPreservesFeishuSiblingAccounts — asserts deep-merge of feishu accounts: default and acme survive untouched; main.verificationToken survives; main.appId / main.appSecret are still normalized.

The existing render-path test (TestRenderCompiledOpenClawPayloadRendersChannelsAsKeyedConfigMap) continues to pass unchanged, proving the env-render allowlist is not affected.

@naiqus naiqus changed the title Fix channel form editors silently dropping unknown fields on save fix: channel form editors silently dropping unknown fields on save Apr 24, 2026
@naiqus naiqus force-pushed the fix/openclaw-channel-editor-field-dropping branch from e2ab157 to 52090a6 Compare April 24, 2026 01:21
@naiqus naiqus changed the title fix: channel form editors silently dropping unknown fields on save Fix channel editors silently dropping unknown fields on save Apr 24, 2026
@naiqus naiqus changed the title Fix channel editors silently dropping unknown fields on save fix: channel editors silently dropping unknown fields on save Apr 24, 2026
@naiqus naiqus marked this pull request as ready for review April 24, 2026 01:26
@naiqus naiqus force-pushed the fix/openclaw-channel-editor-field-dropping branch from 1aee7ac to 965f99f Compare April 24, 2026 14:44
@Iamlovingit
Copy link
Copy Markdown
Collaborator

@naiqus Hi, this PR has conflicts. Could you please help resolve them? Thanks!

Every channel form editor (telegram, dingtalk-connector, slack, feishu)
silently drops fields on Save. The user flow that loses data:

  1. Open a channel resource in the JSON tab, add any field not owned
     by the form (e.g. webhook, custom capabilities, additional feishu
     accounts).
  2. Switch to the Form tab and change anything (e.g. rotate a secret).
  3. Click Save.
  4. The field from step 1 is gone from the saved config — and stays
     gone after a reload.

The data loss occurs in two places; both must be fixed:

Frontend (OpenClawConfigCenterPage.tsx):
  Each update*ChannelContentText rebuilt the output from a hard-coded
  allowlist instead of merging form fields into the parsed config. Any
  field not in the allowlist was dropped on Save. For feishu this
  collapsed a multi-account config to a single `main` account.

Backend (openclaw_config_service.go):
  normalizeOpenClawResourceContent calls normalize*ChannelConfigForEnv
  on every create/update to validate and rebuild the stored config.
  Those helpers use the same allowlist pattern, so even if the
  frontend sends a complete config, the backend strips tenant-authored
  keys before persisting.

Fix:

  - Frontend: introduce a single mergeChannelConfig(existing, allowlisted)
    helper and route every editor's output through it. Each handler
    builds an allowlisted object containing only its known keys; the
    helper overlays it onto the parsed existing config so unknown keys
    survive. The merge point is now structural rather than a per-handler
    convention, so a new editor (e.g. an MS Teams handler added in a
    downstream patch) can't regress by forgetting the spread. Feishu's
    accounts deep-merge is preserved by building the merged accounts
    inside the allowlisted object before handing off to the helper.

  - Backend: introduce mergeOpenClawChannelConfigForStorage, which
    overlays the allowlist-normalized output onto the original parsed
    payload at storage time. Normalized keys still win; unknown keys
    pass through. For feishu the accounts map is merged member-wise.

Env-rendering semantics are unchanged. appendCompiledOpenClawEnvPayload
continues to call the *ForEnv helpers at render time against the
stored content, so the runtime env payload sees the same allowlist as
before.

Tests:

  - Two existing ResourcePayloadFromModelNormalizesStored*JSON tests
    asserted the pre-fix behavior (dropping `legacyField`) and are
    updated to assert it survives.
  - New TestNormalizeOpenClawResourceContentPreservesUnknownChannelFields
    (table-driven, three editors) guards the preservation invariant.
  - New TestNormalizeOpenClawResourceContentPreservesFeishuSiblingAccounts
    guards the feishu deep-merge.

Manual verification against a live instance: add `webhook` via the
JSON tab, switch to Form tab, rotate a secret, Save, reload — the
webhook field is retained.
@naiqus naiqus force-pushed the fix/openclaw-channel-editor-field-dropping branch from 965f99f to 87fe5a6 Compare April 26, 2026 15:50
@naiqus
Copy link
Copy Markdown
Contributor Author

naiqus commented Apr 26, 2026

Sure. It has been resolved now.

litiantian03 and others added 7 commits April 27, 2026 09:43
Kubernetes defaults imagePullPolicy to Always when the image tag is
:latest. This causes pod creation failures in air-gapped and enterprise
environments where nodes cannot reach external registries.

Changes:
- Add ImagePullPolicy field to PodConfig struct
- Default to IfNotPresent in CreatePod when not explicitly set
- Support IMAGE_PULL_POLICY env var for operator override
- Apply policy to both CreateInstance and StartInstance paths

Closes Yuan-lab-LLM#94
fix(instances): delete legacy network policy before create/start instead of ensuring default
…y-configurable

fix: default imagePullPolicy to IfNotPresent for air-gapped environments
Every channel form editor (telegram, dingtalk-connector, slack, feishu)
silently drops fields on Save. The user flow that loses data:

  1. Open a channel resource in the JSON tab, add any field not owned
     by the form (e.g. webhook, custom capabilities, additional feishu
     accounts).
  2. Switch to the Form tab and change anything (e.g. rotate a secret).
  3. Click Save.
  4. The field from step 1 is gone from the saved config — and stays
     gone after a reload.

The data loss occurs in two places; both must be fixed:

Frontend (OpenClawConfigCenterPage.tsx):
  Each update*ChannelContentText rebuilt the output from a hard-coded
  allowlist instead of merging form fields into the parsed config. Any
  field not in the allowlist was dropped on Save. For feishu this
  collapsed a multi-account config to a single `main` account.

Backend (openclaw_config_service.go):
  normalizeOpenClawResourceContent calls normalize*ChannelConfigForEnv
  on every create/update to validate and rebuild the stored config.
  Those helpers use the same allowlist pattern, so even if the
  frontend sends a complete config, the backend strips tenant-authored
  keys before persisting.

Fix:

  - Frontend: introduce a single mergeChannelConfig(existing, allowlisted)
    helper and route every editor's output through it. Each handler
    builds an allowlisted object containing only its known keys; the
    helper overlays it onto the parsed existing config so unknown keys
    survive. The merge point is now structural rather than a per-handler
    convention, so a new editor (e.g. an MS Teams handler added in a
    downstream patch) can't regress by forgetting the spread. Feishu's
    accounts deep-merge is preserved by building the merged accounts
    inside the allowlisted object before handing off to the helper.

  - Backend: introduce mergeOpenClawChannelConfigForStorage, which
    overlays the allowlist-normalized output onto the original parsed
    payload at storage time. Normalized keys still win; unknown keys
    pass through. For feishu the accounts map is merged member-wise.

Env-rendering semantics are unchanged. appendCompiledOpenClawEnvPayload
continues to call the *ForEnv helpers at render time against the
stored content, so the runtime env payload sees the same allowlist as
before.

Tests:

  - Two existing ResourcePayloadFromModelNormalizesStored*JSON tests
    asserted the pre-fix behavior (dropping `legacyField`) and are
    updated to assert it survives.
  - New TestNormalizeOpenClawResourceContentPreservesUnknownChannelFields
    (table-driven, three editors) guards the preservation invariant.
  - New TestNormalizeOpenClawResourceContentPreservesFeishuSiblingAccounts
    guards the feishu deep-merge.

Manual verification against a live instance: add `webhook` via the
JSON tab, switch to Form tab, rotate a secret, Save, reload — the
webhook field is retained.
…om:naiqus/ClawManager into fix/openclaw-channel-editor-field-dropping
TestNormalizeOpenClawResourceContentPreservesUnknownChannelFields and
TestNormalizeOpenClawResourceContentPreservesFeishuSiblingAccounts were
declared twice causing build failure.
@Iamlovingit
Copy link
Copy Markdown
Collaborator

/lgtm

@Iamlovingit
Copy link
Copy Markdown
Collaborator

@naiqus Thanks your contributions!

@Iamlovingit Iamlovingit merged commit 58c35f6 into Yuan-lab-LLM:main Apr 29, 2026
5 checks passed
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.

3 participants