Skip to content

fix: 4 bug fixes for feishu, health, models, and skills#63924

Closed
hubbyxiao-cpu wants to merge 4 commits into
openclaw:mainfrom
hubbyxiao-cpu:fix/bugfixes-2026-04-10
Closed

fix: 4 bug fixes for feishu, health, models, and skills#63924
hubbyxiao-cpu wants to merge 4 commits into
openclaw:mainfrom
hubbyxiao-cpu:fix/bugfixes-2026-04-10

Conversation

@hubbyxiao-cpu

Copy link
Copy Markdown

Summary

4 bug fixes addressing stability issues in OpenClaw 2026.4.9.

1. fix(feishu): use buildChannelConfigSchema instead of hand-written JSON Schema

  • Problem: Hand-written JSON Schema only listed 11 properties for accounts, while the Zod schema defines 36+ properties. This caused must NOT have additional properties errors when valid config keys like dmPolicy, allowFrom, etc. were present.
  • Fix: Switch to buildChannelConfigSchema(FeishuConfigSchema) to auto-generate the JSON Schema from the Zod schema, keeping them in sync. This matches the pattern used by all other channel plugins.

2. fix(health): use readBestEffortConfig for fresh config in getHealthSnapshot

  • Problem: getHealthSnapshot() used loadConfig() which returns a stale runtimeConfigSnapshot in the gateway process. This caused health checks to show channels as "not configured" even when the config file had been updated.
  • Fix: Read directly from file via await readBestEffortConfig() with loadConfig() as fallback.

3. fix(models): normalize volcengine-plan and byteplus-plan in normalizeProviderId

  • Problem: normalizeProviderId() did not normalize volcengine-plan to volcengine or byteplus-plan to byteplus, causing contextWindow lookups to fail for coding-plan providers.
  • Fix: Add the normalization mappings to normalizeProviderId() and simplify normalizeProviderIdForAuth() to delegate to it.

4. fix(skills): use original path for symlinked skills instead of skipping

  • Problem: resolveContainedSkillPath() skipped skills when their realpath resolved outside the configured root (e.g. pnpm symlinks, macOS /var -> /private/var).
  • Fix: Fall back to the original (unresolved) path when it is inside the root, logging a warning instead of skipping entirely.

Test plan

  • pnpm build passes (only pre-existing global-singleton.ts error)
  • normalizeProviderId tests pass
  • normalizeProviderIdForAuth tests pass
  • context.lookup tests pass
  • ir.blockquote-spacing tests pass

…N Schema

The hand-written JSON Schema in feishu channel.ts only listed 11 properties
for accounts, while the Zod schema (FeishuAccountConfigSchema) defines 36+
properties via FeishuSharedConfigShape. This caused 'must NOT have additional
properties' errors when valid config keys like dmPolicy, allowFrom, etc.
were present.

Switch to buildChannelConfigSchema(FeishuConfigSchema) to auto-generate
the JSON Schema from the Zod schema, keeping them in sync automatically.
This matches the pattern used by all other channel plugins (telegram,
discord, slack, signal, etc.).
…apshot

getHealthSnapshot() used loadConfig() which returns a stale
runtimeConfigSnapshot in the gateway process. This caused health
checks to show channels as 'not configured' even when the config
file had been updated. Now reads directly from file via
readBestEffortConfig() with loadConfig() as fallback.
…ProviderId

normalizeProviderId() did not normalize volcengine-plan to volcengine
or byteplus-plan to byteplus, causing contextWindow lookups to fail
for coding-plan providers. The qualified cache key
volcengine-plan/ark-code-latest could not match volcengine/ark-code-latest
in the model cache. Simplify normalizeProviderIdForAuth to delegate
to normalizeProviderId now that the mapping is centralized.
resolveContainedSkillPath() skipped skills when their realpath resolved
outside the configured root (e.g. pnpm symlinks, macOS /var -> /private/var).
This caused valid skills to be silently dropped. Now falls back to the
original (unresolved) path when it is inside the root, logging a warning
instead of skipping entirely.
@openclaw-barnacle openclaw-barnacle Bot added commands Command implementations agents Agent runtime and tooling channel: feishu Channel integration: feishu size: S labels Apr 9, 2026
@greptile-apps

greptile-apps Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR bundles four bug fixes: (1) Feishu channel config schema auto-generated from Zod schema, (2) health snapshot reads fresh config via readBestEffortConfig, (3) volcengine-plan/byteplus-plan provider normalization, and (4) symlinked skills fall back to original path instead of being silently skipped.

Fixes 1, 3, and 4 are correct and low-risk. Fix 2 (health) has a logic error: readBestEffortConfig returns loadConfig() when the config file is valid, which in the gateway process still resolves to the stale runtimeConfigSnapshot. The described problem (stale config in health checks) remains unfixed in the gateway; see the inline comment for details.

Confidence Score: 4/5

Safe to merge for fixes 1, 3, 4; fix 2 (health) should be revisited — it does not regress behavior but leaves the described bug unfixed.

Three of the four fixes are correct and verified against existing patterns. Fix 2 has a P1 logic issue: readBestEffortConfig returns the same stale runtimeConfigSnapshot as loadConfig() does in the gateway, so the health staleness bug is not actually resolved. The ?? loadConfig() fallback is also dead code. No behavior regression, but the stated fix goal is not achieved.

src/commands/health.ts — the readBestEffortConfig call at lines 353–354 does not achieve fresh-config reads in the gateway process.

Vulnerabilities

No security concerns identified. The skills symlink fallback logs a warning and only widens the path boundary to the logical (unresolved) root, not beyond it; fully-escaped symlinks are still rejected.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/commands/health.ts
Line: 353-354

Comment:
**`readBestEffortConfig` still returns stale config in gateway**

`readBestEffortConfig` is implemented as:

```ts
// src/config/io.ts:1494
export async function readBestEffortConfig(): Promise<OpenClawConfig> {
  const snapshot = await readConfigFileSnapshot();
  return snapshot.valid ? loadConfig() : snapshot.config;
}
```

When the config file is valid (the normal case), it returns `loadConfig()`. In the gateway process `loadConfig()` returns the in-memory `runtimeConfigSnapshot`, which is exactly the stale value the PR says it wants to avoid. The fix is a no-op for the described problem in the gateway path.

Additionally, `readBestEffortConfig` returns `Promise<OpenClawConfig>` (never `null` or `undefined`), so the `?? loadConfig()` fallback is unreachable dead code and will never be evaluated.

In the unusual edge case where the config file is temporarily malformed, the new code returns `snapshot.config` (an empty or partially-parsed object) instead of the stale-but-valid runtime snapshot — a potential regression.

To actually read a fresh config in the gateway, something like `snapshot.config` from `readConfigFileSnapshot()` (when `snapshot.valid`) would need to be used directly, bypassing `loadConfig()`.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(skills): use original path for symli..." | Re-trigger Greptile

Comment thread src/commands/health.ts
Comment on lines +353 to +354
const rawCfg = await readBestEffortConfig();
const cfg = rawCfg ?? loadConfig();

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.

P1 readBestEffortConfig still returns stale config in gateway

readBestEffortConfig is implemented as:

// src/config/io.ts:1494
export async function readBestEffortConfig(): Promise<OpenClawConfig> {
  const snapshot = await readConfigFileSnapshot();
  return snapshot.valid ? loadConfig() : snapshot.config;
}

When the config file is valid (the normal case), it returns loadConfig(). In the gateway process loadConfig() returns the in-memory runtimeConfigSnapshot, which is exactly the stale value the PR says it wants to avoid. The fix is a no-op for the described problem in the gateway path.

Additionally, readBestEffortConfig returns Promise<OpenClawConfig> (never null or undefined), so the ?? loadConfig() fallback is unreachable dead code and will never be evaluated.

In the unusual edge case where the config file is temporarily malformed, the new code returns snapshot.config (an empty or partially-parsed object) instead of the stale-but-valid runtime snapshot — a potential regression.

To actually read a fresh config in the gateway, something like snapshot.config from readConfigFileSnapshot() (when snapshot.valid) would need to be used directly, bypassing loadConfig().

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/health.ts
Line: 353-354

Comment:
**`readBestEffortConfig` still returns stale config in gateway**

`readBestEffortConfig` is implemented as:

```ts
// src/config/io.ts:1494
export async function readBestEffortConfig(): Promise<OpenClawConfig> {
  const snapshot = await readConfigFileSnapshot();
  return snapshot.valid ? loadConfig() : snapshot.config;
}
```

When the config file is valid (the normal case), it returns `loadConfig()`. In the gateway process `loadConfig()` returns the in-memory `runtimeConfigSnapshot`, which is exactly the stale value the PR says it wants to avoid. The fix is a no-op for the described problem in the gateway path.

Additionally, `readBestEffortConfig` returns `Promise<OpenClawConfig>` (never `null` or `undefined`), so the `?? loadConfig()` fallback is unreachable dead code and will never be evaluated.

In the unusual edge case where the config file is temporarily malformed, the new code returns `snapshot.config` (an empty or partially-parsed object) instead of the stale-but-valid runtime snapshot — a potential regression.

To actually read a fresh config in the gateway, something like `snapshot.config` from `readConfigFileSnapshot()` (when `snapshot.valid`) would need to be used directly, bypassing `loadConfig()`.

How can I resolve this? If you propose a fix, please make it concise.

@clawsweeper

clawsweeper Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I did a careful shell check against current main, and the useful part of this older PR is already implemented there.

This stale, conflicting PR should close because current main already has the useful Feishu config-schema generation, provider-plan auth/catalog handling, runtime health config path, and explicit trusted symlink support; the remaining branch diff is obsolete and riskier than current main.

So I’m closing this older PR as already covered on main rather than keeping a mostly-duplicated branch open.

Review details

Best possible solution:

Keep the current main implementations and close this stale branch; any remaining reproducible bug should come back as a narrow PR against the current files with real behavior proof.

Do we have a high-confidence way to reproduce the issue?

No high-confidence current-main failing reproduction was established. Source inspection shows current main covers the useful behavior with newer paths, while the stale PR branch itself remains conflicting and lacks real behavior proof.

Is this the best way to solve the issue?

No. Current main is the better solution shape: Feishu schema generation is in the plugin, plan-provider credentials use manifest aliases, health uses runtime snapshots, and symlinked skills require explicit trusted targets.

Security review:

Security review needs attention: The stale branch includes a concrete security-boundary concern in skill symlink loading, which current main handles more narrowly with explicit trusted target roots.

  • [medium] Avoid broadening skill symlink containment — src/agents/skills/workspace.ts:211
    Returning the unresolved path when the real path escapes the configured root would allow a symlink under a skill root to load content outside that root without the current explicit allowSymlinkTargets trust decision.
    Confidence: 0.78

AGENTS.md: found and applied where relevant.

What I checked:

  • Source: Feishu schema generation is already on main: Current main wires the Feishu channel config schema to buildChannelConfigSchema(FeishuConfigSchema), replacing the stale hand-written schema targeted by this PR. (extensions/feishu/src/channel.ts:690, c78f9376d929)
  • Source: health now uses the runtime health config path: getHealthSnapshot reads through readRuntimeHealthConfig(), and current config loading documents that long-lived runtimes should update snapshots through explicit reload/watcher paths rather than hot-path JSON reparses. (src/commands/health.ts:467, c78f9376d929)
  • Source: coding-plan providers are manifest-owned aliases: Current BytePlus and Volcengine manifests declare providerAuthAliases for byteplus-plan and volcengine-plan while keeping plan providers in their own model catalogs, avoiding the PR's global provider-id conflation. (extensions/byteplus/openclaw.plugin.json:17, c78f9376d929)
  • Source: trusted symlink support exists with an explicit config boundary: Current skill loading allows symlinks whose real target is under configured allowSymlinkTargets instead of loading any symlink whose unresolved path is inside the source root. (src/skills/loading/workspace.ts:463, c78f9376d929)
  • Tests/docs: symlink behavior is documented and covered: Current tests cover configured external symlink targets, and docs describe skills.load.allowSymlinkTargets as the supported path for intentional symlinked layouts. (src/skills/loading/workspace-load.test.ts:536, c78f9376d929)
  • PR state and prior review context: Live GitHub data shows the PR is open but CONFLICTING/DIRTY, has no closing issue references, and prior review flagged the health change as ineffective because readBestEffortConfig still went through the pinned runtime snapshot on that branch. (b2dac58717f6)

Likely related people:

  • steipete: Peter Steinberger authored the providerAuthAliases support used by the current BytePlus/Volcengine plan-provider solution and the Feishu chat-plugin builder refactor that carries the current schema wiring. (role: feature-history owner; confidence: medium; commits: 9e4f478f866c, 3365f2e15768; files: extensions/byteplus/openclaw.plugin.json, extensions/volcengine/openclaw.plugin.json, extensions/feishu/src/channel.ts)
  • zengLingbiao: Current-main blame in this checkout attributes the present Feishu, health, skills, provider-id, and provider manifest lines to the current-main proof boundary commit. (role: recent area contributor; confidence: low; commits: 7fb0d45b48e1; files: extensions/feishu/src/channel.ts, src/commands/health.ts, src/skills/loading/workspace.ts)
  • luoyanglang: Opened the related open CI PR that checks bundled channel config metadata staleness, which is adjacent to the Feishu schema-generation part of this PR. (role: adjacent follow-up owner; confidence: medium; commits: dab9eb9509c6; files: .github/workflows/ci.yml)

Codex review notes: model internal, reasoning high; reviewed against c78f9376d929; fix evidence: commit 7fb0d45b48e1, main fix timestamp 2026-06-14T10:49:26+08:00.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels May 19, 2026
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 19, 2026
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat.

Where did the egg go?
  • The egg game starts only after the PR passes the real-behavior proof check.
  • Before that, no creature or rarity is rolled. The treat waits for real proof.
  • This is still just collectible flavor: proof affects review readiness, not creature quality.

@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Jun 4, 2026
@clawsweeper clawsweeper Bot added rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 4, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Jun 5, 2026
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. labels Jun 14, 2026
@clawsweeper

clawsweeper Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

@clawsweeper clawsweeper Bot closed this Jun 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling channel: feishu Channel integration: feishu commands Command implementations merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. P2 Normal backlog priority with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant