Skip to content

agents: bootstrap non-main agent models.json from main when provider plan is empty#58391

Closed
rogerdigital wants to merge 1 commit into
openclaw:mainfrom
rogerdigital:fix/non-main-agent-models-json-bootstrap
Closed

agents: bootstrap non-main agent models.json from main when provider plan is empty#58391
rogerdigital wants to merge 1 commit into
openclaw:mainfrom
rogerdigital:fix/non-main-agent-models-json-bootstrap

Conversation

@rogerdigital

@rogerdigital rogerdigital commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: When planOpenClawModelsJson returns { action: "skip" } for a non-main agent (provider resolution finds zero providers), models.json is never written. The model registry stays empty and any subsequent inference call fails with "Unknown model".
  • Why it matters: Users who create non-main agents (e.g. background workers, embedded Pi agents) are blocked from starting sessions entirely when no provider is explicitly configured for that agent.
  • What changed: Added a bootstrap fallback in the plan.action === "skip" branch of ensureOpenClawModelsJson. When the target is a non-main agent directory and has no existing models.json, the main agent's models.json is copied in. Includes idempotency guards, error surfacing, retry-after-main-appears logic, and preservation of existing/malformed non-main files.
  • What did NOT change (scope boundary): planOpenClawModelsJson itself is untouched. The normal noop / write paths are unchanged. Main agent behavior is unchanged. No config schema or CLI surface changes.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Other: Agent runtime — model registry bootstrap

Linked Issue/PR

Root Cause / Regression History

  • Root cause: ensureOpenClawModelsJson treated plan.action === "skip" as a terminal no-op for all agents. For non-main agents where provider resolution returns empty, this meant models.json was never created, leaving the model registry empty at runtime.
  • Missing detection / guardrail: No fallback path existed for the non-main + empty-provider combination. The skip branch assumed the caller would handle the missing file, but no caller did.
  • Prior context: The skip action was introduced as a valid early-exit when provider discovery has nothing to write. It works correctly for the main agent (which typically already has a models.json from initial setup), but non-main agents created via agents.create start with an empty directory.
  • Why this regressed now: Not a regression from a specific commit — this is a latent bug exposed when non-main agent usage grew after the embedded Pi agent feature landed.
  • If unknown, what was ruled out: N/A — root cause is clear.

Regression Test Plan

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/agents/models-config.non-main-bootstrap.test.ts
  • Scenario the test should lock in:
    1. Non-main agent with no models.json + main agent has one → bootstrap copies it
    2. Main agent also missing models.json → graceful skip (no crash)
    3. Main agent dir === target dir → no self-bootstrap
    4. Repeated calls → idempotent (no rewrite, mtime stable)
    5. Non-main already has models.json → preserved, not overwritten
    6. Write failure (e.g. parent is a file) → error propagated, not swallowed
    7. Main models.json appears after initial miss → retry succeeds
    8. Main unreadable but non-main already exists → non-main preserved
    9. Malformed non-main models.json → preserved, not overwritten
    10. Unreadable non-main models.json (permission error) → error surfaced
  • Why this is the smallest reliable guardrail: The bootstrap is a pure function of filesystem state (main exists? non-main exists? content match?). Unit tests with temp dirs cover all branches without needing a running gateway.
  • Existing test that already covers this: None — the skip branch had no non-main-specific coverage.

User-visible / Behavior Changes

  • Non-main agents that previously failed with "Unknown model" at session start will now silently bootstrap from the main agent's models.json and start successfully.
  • No config changes, no new CLI flags, no new env vars.

Diagram

Before:
  planOpenClawModelsJson(non-main) -> { action: "skip" }
    -> return { wrote: false }
    -> models.json missing
    -> runtime: "Unknown model" ✗

After:
  planOpenClawModelsJson(non-main) -> { action: "skip" }
    -> is non-main agent?
       -> has existing models.json? -> preserve it, return { wrote: false }
       -> read main agent models.json
          -> main missing/empty? -> return { wrote: false } (retry on next call)
          -> main has content? -> copy to non-main, return { wrote: true } ✓

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? Yes — see below
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • Explanation:
    • Secret propagation (Aisle Login fails with 'WebSocket Error (socket hang up)' ECONNRESET #2): The bootstrap copies the main agent's models.json (which may contain API keys) into the non-main agent directory. This is safe in the current threat model because: (a) both directories are under ~/.openclaw/agents/ owned by the same OS user with mode 0700; (b) non-main agents are spawned by the same gateway process that owns the main agent; (c) the file is written with mode 0600 via ensureModelsFileMode. There is no cross-user or cross-tenant boundary being crossed.
    • Path traversal via agentDirOverride (Aisle WA business, groups & office hours  #3): agentDirOverride is only passed from internal callers (runEmbeddedPiAgent), never from external/user input. The resolveOpenClawAgentDir() fallback resolves from env vars set by the gateway itself. No API surface accepts arbitrary agentDir from untrusted sources.
    • Path traversal in bundled-plugin-build-entries.mjs (Aisle fix: add @lid format support and allowFrom wildcard handling #1): Pre-existing issue unrelated to this PR — sourceEntries comes from repo-owned package.json files, not user input. Flagged for separate tracking.

Repro + Verification

Environment

Steps

  1. Ensure main agent has a valid ~/.openclaw/agents/main/agent/models.json.
  2. Create a non-main agent via agents.create (or manually create an agent dir without models.json).
  3. Start a session using that non-main agent.

Expected

  • Non-main agent bootstraps models.json from main and session starts successfully.

Actual (before fix)

  • models.json is never written, runtime fails with Unknown model: <provider>/<model>.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers

Test output:

 ✓ src/agents/models-config.non-main-bootstrap.test.ts (11 tests)
   ✓ copies main agent models.json when plan skips for a non-main agent
   ✓ returns skip when main agent also has no models.json
   ✓ does not bootstrap when agentDir is the main agent dir
   ✓ does not rewrite an existing non-main models.json on repeated calls
   ✓ does not overwrite an existing non-main models.json when plan skips
   ✓ propagates bootstrap write errors instead of silently skipping
   ✓ retries bootstrap after the main models.json appears later
   ✓ retries bootstrap after an empty main models.json gets populated
   ✓ does not depend on main readability when non-main models.json already exists
   ✓ preserves malformed non-main models.json
   ✓ surfaces unreadable existing non-main models.json

Human Verification (required)

  • Verified scenarios:
    • Created a non-main agent dir without models.json, ran ensureOpenClawModelsJson → file bootstrapped from main, content matches
    • Ran same call again → wrote: false, mtime unchanged (idempotency confirmed)
    • Deleted main models.json, ran for non-main → graceful skip, no crash
    • Pre-populated non-main with different content → not overwritten
    • pnpm check passes, pnpm test -- src/agents/models-config passes
  • Edge cases checked:
    • Main agent dir is a broken symlink → ENOENT handled, falls through to skip
    • Parent of non-main agent dir is a regular file → error propagated (not swallowed)
    • Non-main models.json contains invalid JSON → preserved as-is
    • Non-main models.json unreadable (permission denied) → error surfaced
  • What I did not verify:

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Follow-up fixes addressing bot feedback:

  • f12a5f2 — preserve existing non-main models.json, stop swallowing bootstrap write failures, add idempotency coverage
  • 83a3f32 — retry bootstrap after main file appears later, avoid depending on main readability when non-main already has content
  • 1530130 — preserve malformed non-main models.json
  • 962454e — surface unreadable existing non-main models.json instead of treating as missing
  • 6f04c34 — mark empty bootstrap source as non-cacheable so retry picks up later content

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: Bootstrap copies API keys from main to non-main agent directory.
    • Mitigation: Both dirs are under the same ~/.openclaw/agents/ tree, same OS user, mode 0600. No cross-trust-boundary exposure. If future multi-tenant support is added, this path should be revisited.
  • Risk: Main agent models.json is stale or incomplete when copied.
    • Mitigation: Bootstrap only fires when non-main has no models.json at all. Once bootstrapped, subsequent ensureOpenClawModelsJson calls with real provider resolution will overwrite via the normal write path.

@aisle-research-bot

aisle-research-bot Bot commented Mar 31, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 3 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Non-main agents bootstrap models.json by copying main agent provider API keys
2 🟡 Medium Malformed models.json can block non-main bootstrap (persistent config poisoning / DoS)
3 🔵 Low Filesystem path disclosure via stack trace on models.json read errors
1. 🟠 Non-main agents bootstrap models.json by copying main agent provider API keys
Property Value
Severity High
CWE CWE-200
Location src/agents/models-config.ts:206-236

Description

ensureOpenClawModelsJson() now bootstraps a non-main agent’s models.json by reading and copying the main agent’s models.json verbatim when planOpenClawModelsJson() returns { action: "skip" }.

This can expose sensitive provider credentials (e.g., apiKey) to other agent directories:

  • Input (secret source): mainAgentDir/models.json may contain provider apiKey fields (confirmed by the added test seeding apiKey: "sk-test").
  • Sink (propagation): the file contents are written into the non-main agent directory via writeModelsFileAtomicForModelsJson(targetPath, mainContents).
  • Security impact: any process/user with access to the non-main agent directory (e.g., worker/subagent runtime, plugins, tools) can now read the main agent’s provider keys, increasing blast radius if the non-main agent is less trusted.

Vulnerable code (verbatim copy):

const mainModelsPath = path.join(mainAgentDir, "models.json");
mainContents = await fs.readFile(mainModelsPath, "utf-8");
...
await writeModelsFileAtomicForModelsJson(targetPath, mainContents);

Even though the file mode is set to 0600, the code still duplicates secrets across agent boundaries rather than scoping them to the agent that should own them.

Recommendation

Avoid copying secrets from the main agent to non-main agents.

Safer options:

  1. Bootstrap only non-secret registry data (e.g., models list) and omit provider credentials when copying.
  2. If non-main agents must use the same providers, reference secrets indirectly (e.g., via env vars, OS keychain, or a shared secret store with explicit policy), not by duplicating plaintext keys into each agent’s models.json.
  3. Add an explicit guard/flag to enable this behavior only when the deployment model guarantees same trust boundary.

Example: strip apiKey fields before writing to the non-main agent:

const parsed = JSON.parse(mainContents);
if (parsed?.providers && typeof parsed.providers === "object") {
  for (const provider of Object.values(parsed.providers)) {
    if (provider && typeof provider === "object") {
      delete (provider as any).apiKey;
    }
  }
}
await writeModelsFileAtomicForModelsJson(targetPath, JSON.stringify(parsed, null, 2) + "\n");

(Adjust to your actual provider secret fields / secret-ref mechanism.)

2. 🟡 Malformed models.json can block non-main bootstrap (persistent config poisoning / DoS)
Property Value
Severity Medium
CWE CWE-20
Location src/agents/models-config.ts:206-214

Description

ensureOpenClawModelsJson() now treats any non-empty existing models.json as authoritative for non-main agents even when it is not valid JSON.

  • readExistingModelsFile() returns the raw file contents even when JSON.parse fails, setting parsed: null but leaving raw non-empty.
  • In the plan.action === "skip" path for non-main agents, the bootstrap decision only checks existingModelsFile.raw.trim().
  • As a result, a malformed but non-empty models.json prevents bootstrapping/correction from the main agent’s models.json, potentially leaving the worker with an unusable registry and causing repeated downstream failures or empty model discovery.

Vulnerable flow:

  • Input: models.json file contents from agentDir (filesystem)
  • Condition: parsing fails but file is non-empty
  • Effect: bootstrap is skipped (wrote: false) and invalid config persists

Recommendation

Treat malformed JSON as not a valid existing configuration for purposes of the non-main bootstrap decision.

Options:

  1. Revert to clearing raw on parse failure (or add a parseError flag) and only skip bootstrap when parsing succeeded:
const existingModelsFile = await readExistingModelsFile(targetPath);

const hasValidExisting = existingModelsFile.parsed !== null;

if (agentDir !== mainAgentDir) {
  if (hasValidExisting) {// keep existing
    return ...;
  }// bootstrap from main
}
  1. If the file is non-empty but invalid JSON, overwrite it from main (or from a freshly planned plan.contents) and optionally log a warning for operator visibility.
3. 🔵 Filesystem path disclosure via stack trace on models.json read errors
Property Value
Severity Low
CWE CWE-209
Location src/commands/models/list.list-command.ts:57-60

Description

ensureOpenClawModelsJson() now propagates underlying fs.readFile errors (e.g., EACCES, EPERM) when reading an existing models.json. The models list CLI command catches these errors and prints the full stack trace to the user.

This can disclose sensitive local filesystem paths and other runtime details contained in Node.js error stacks (and potentially error messages), for example:

  • input: unreadable ${agentDir}/models.json (permission/IO error)
  • propagation: ensureOpenClawModelsJson() throws the original error
  • sink: CLI prints err.stack via formatErrorWithStack(err)

Vulnerable sink:

runtime.error(`Model registry unavailable:\n${formatErrorWithStack(err)}`);

If this output can reach untrusted log collectors or be shared in support bundles, it may unintentionally leak hostnames, usernames, directory layouts, etc.

Recommendation

Avoid printing full stack traces (which often include absolute paths) by default. Prefer a sanitized message, optionally gated behind a debug flag.

Example:

} catch (err) {
  const message = err instanceof Error ? err.message : String(err);
  runtime.error(`Model registry unavailable: ${message}`);// Optionally: if (runtime.debug) runtime.error(err instanceof Error ? err.stack ?? "" : "");
  process.exitCode = 1;
  return;
}

Also consider mapping common filesystem errors (e.g., EACCES) to user-friendly guidance without including the full path unless explicitly requested.


Analyzed PR: #58391 at commit 02b6411

Last updated on: 2026-04-09T04:39:17Z

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S labels Mar 31, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b2454e5d6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/models-config.ts Outdated
Comment thread src/agents/models-config.ts Outdated
@greptile-apps

greptile-apps Bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a bootstrap fallback in ensureOpenClawModelsJson: when planOpenClawModelsJson returns { action: \"skip\" } for a non-main agent (empty provider set), the function now copies the main agent's models.json into the non-main agent directory so the model registry is not empty. A new test file covers three scenarios and passes.

Key findings:

  • P1 — Fingerprint cache never stabilizes after bootstrap (src/agents/models-config.ts): The fingerprint includes the target file's modelsFileMtimeMs. The bootstrap path writes unconditionally (no content comparison), so every write changes the mtime, which changes the fingerprint, which causes a cache miss on the next call, which triggers another write. The fix is to compare existingModelsFile.raw with mainContents before writing — identical to the noop check already performed inside planOpenClawModelsJson for the normal write path.

  • P2 — Tests don't cover second-call idempotency (src/agents/models-config.non-main-bootstrap.test.ts): Each test calls the function only once, so the cache-instability loop is not caught by the test suite.

Confidence Score: 4/5

Needs one content-equality guard before merging; without it the fingerprint cache never stabilizes and every call for a non-main agent unnecessarily rewrites the file.

One P1 defect: the bootstrap write is unconditional, breaking the mtime-based fingerprint cache contract. The core intent and guard conditions are correct; the fix is a small early-return analogous to the existing noop path in planOpenClawModelsJson.

src/agents/models-config.ts lines 180–186: add an existingModelsFile.raw === mainContents early-return before calling writeModelsFileAtomic.

Important Files Changed

Filename Overview
src/agents/models-config.ts Adds bootstrap fallback in the plan.action === "skip" branch, but writes unconditionally — the fingerprint (which includes the target file's mtime) never stabilizes, so every successive call re-writes the file and returns wrote: true even when content is unchanged.
src/agents/models-config.non-main-bootstrap.test.ts New test file covering three bootstrap scenarios; all pass but none exercise idempotency (second-call cache hit), which is the scenario where the cache-instability bug manifests.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/models-config.ts
Line: 180-186

Comment:
**Bootstrap writes unconditionally, breaking cache stability**

The fingerprint is built from `modelsFileMtimeMs` of the non-main agent's `models.json` (see `buildModelsJsonFingerprint`). Because the bootstrap always writes (even when the content hasn't changed), the following loop occurs on every successive call:

1. Call N: `modelsFileMtimeMs = T(N-1)` → fingerprint F(N) ≠ F(N-1) (cache miss)
2. Plan still returns `"skip"` (providers are still empty for the non-main agent)
3. Bootstrap writes main's content again → mtime becomes T(N), fingerprint would be F(N+1)
4. Repeat

In practice this means each call to `ensureOpenClawModelsJson` for a non-main bootstrapped agent (e.g. every run start in `pi-embedded-runner/run.ts` and every compact in `pi-embedded-runner/compact.ts`) rewrites the file unnecessarily and returns `wrote: true` even though the content is identical.

The fix mirrors how `planOpenClawModelsJson` avoids this with its own `noop` path — compare `existingModelsFile.raw` against `mainContents` before writing:

```
const mainContents = await fs.readFile(mainModelsPath, "utf-8");
if (mainContents.trim()) {
  if (existingModelsFile.raw === mainContents) {
    await ensureModelsFileMode(targetPath);
    return { fingerprint, result: { agentDir, wrote: false } };
  }
  await fs.mkdir(agentDir, { recursive: true, mode: 0o700 });
  await writeModelsFileAtomic(targetPath, mainContents);
  await ensureModelsFileMode(targetPath);
  return { fingerprint, result: { agentDir, wrote: true } };
}
```

This stabilises the fingerprint on the second call (once the file exists with the right content, mtime no longer changes, and the cache hits cleanly from then on).

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

---

This is a comment left during a code review.
Path: src/agents/models-config.non-main-bootstrap.test.ts
Line: 26-62

Comment:
**Tests don't exercise second-call cache idempotency**

Each test calls `ensureOpenClawModelsJson` exactly once and then reads the result. This means the fingerprint-instability bug described in the production code comment above passes all three tests undetected.

Consider adding a fourth test that calls `ensureOpenClawModelsJson` twice for the same non-main agent and asserts that the second call returns `{wrote: false}` and does not change the file's mtime:

```typescript
it("does not rewrite on subsequent call when content is unchanged", async () => {
  await withTempHome(async (home) => {
    const mainAgentDir = path.join(home, "agents", "main", "agent");
    const nonMainAgentDir = path.join(home, "agents", "worker", "agent");
    process.env.OPENCLAW_AGENT_DIR = mainAgentDir;
    process.env.PI_CODING_AGENT_DIR = mainAgentDir;

    await fs.mkdir(mainAgentDir, { recursive: true });
    await fs.writeFile(
      path.join(mainAgentDir, "models.json"),
      JSON.stringify({ providers: { openai: {} } }, null, 2),
    );

    const first = await ensureOpenClawModelsJson({}, nonMainAgentDir);
    expect(first.wrote).toBe(true);

    resetModelsJsonReadyCacheForTest();
    const mtimeAfterFirst = (await fs.stat(path.join(nonMainAgentDir, "models.json"))).mtimeMs;
    const second = await ensureOpenClawModelsJson({}, nonMainAgentDir);

    expect(second.wrote).toBe(false);
    const mtimeAfterSecond = (await fs.stat(path.join(nonMainAgentDir, "models.json"))).mtimeMs;
    expect(mtimeAfterSecond).toBe(mtimeAfterFirst);
  });
});
```

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

Reviews (1): Last reviewed commit: "agents: bootstrap non-main agent models...." | Re-trigger Greptile

Comment thread src/agents/models-config.ts Outdated
Comment thread src/agents/models-config.non-main-bootstrap.test.ts
@openclaw-barnacle openclaw-barnacle Bot added scripts Repository scripts size: M and removed size: S labels Mar 31, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f12a5f21e4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/models-config.ts
Comment thread src/agents/models-config.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f12a5f21e4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/models-config.ts
Comment thread src/agents/models-config.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 83a3f32024

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/models-config.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1530130433

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/models-config.ts Outdated

Copy link
Copy Markdown
Contributor Author

All actionable review feedback on this PR has been addressed and the review threads are resolved.

Follow-up fixes landed in:

  • f12a5f2 preserve existing non-main models.json, stop swallowing bootstrap write failures, and add idempotency coverage
  • 83a3f32 retry bootstrap after the main file appears later and avoid depending on main readability when non-main already has content
  • 1530130 preserve malformed non-main models.json
  • 962454e surface unreadable existing non-main models.json instead of treating it as missing

Current state:

  • mergeable = true
  • review threads resolved
  • visible checks are green except install-smoke, which is still pending at the time of this comment

@rogerdigital
rogerdigital force-pushed the fix/non-main-agent-models-json-bootstrap branch from 962454e to 03664d8 Compare April 1, 2026 02:39

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 15c00afc25

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return { cacheable: true, fingerprint, result: { agentDir, wrote: true } };
}
}
return { cacheable: true, fingerprint, result: { agentDir, wrote: false } };

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.

P2 Badge Mark empty bootstrap source as non-cacheable

When main/models.json exists but is empty/whitespace, this branch returns cacheable: true and wrote: false, but the fingerprint only tracks the target agent's files and config (not the main file contents). In a long-lived process, a first call made while main is empty will be cached and subsequent calls for the non-main agent will keep returning the cached skip even after main later gets valid content, so bootstrap never retries and model resolution can stay broken until cache reset/restart.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Handled in 6f04c34. The empty-main bootstrap path now returns cacheable: false, matching the missing-main path (83a3f32), so a long-lived process retries after main gets populated. Added a regression test for the empty-main → populated-main transition.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f4d8135afc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return { cacheable: true, fingerprint, result: { agentDir, wrote: true } };
}
}
return { cacheable: true, fingerprint, result: { agentDir, wrote: false } };

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.

P2 Badge Make empty main bootstrap result non-cacheable

When plan.action === "skip" for a non-main agent and main/models.json exists but is empty, this path returns cacheable: true with wrote: false. Because buildModelsJsonFingerprint only tracks the target agent state (not the main agent file), that cached skip can persist in a long-lived process even after main later writes valid content, so bootstrap is never retried and the non-main agent can remain without a usable models.json.

Useful? React with 👍 / 👎.

@rogerdigital
rogerdigital force-pushed the fix/non-main-agent-models-json-bootstrap branch from f4d8135 to 99c7511 Compare April 1, 2026 09:06
@SonicBotMan

This comment was marked as spam.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 99c7511cff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return { cacheable: true, fingerprint, result: { agentDir, wrote: true } };
}
}
return { cacheable: true, fingerprint, result: { agentDir, wrote: false } };

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.

P2 Badge Mark empty bootstrap source as non-cacheable

When plan.action === "skip" for a non-main agent and main/models.json exists but contains only whitespace, this path returns a cacheable wrote: false result. Because the fingerprint only tracks the target agent state (not the main agent file), a long-lived process can keep serving this cached skip even after main/models.json is later populated, so bootstrap is never retried and the non-main agent can remain without a usable catalog until process restart or unrelated fingerprint change.

Useful? React with 👍 / 👎.

@rogerdigital
rogerdigital force-pushed the fix/non-main-agent-models-json-bootstrap branch from 6f04c34 to eaf826c Compare April 1, 2026 12:35
…plan is empty

When `planOpenClawModelsJson` returns `{ action: "skip" }` for a non-main
agent (provider resolution finds zero providers), the agent's models.json
is never written, leaving the model registry empty and causing "Unknown
model" failures at runtime.

Add a bootstrap fallback: copy the main agent's models.json into the
non-main agent directory when the plan skips and no existing file is
present. Includes idempotency guards, error surfacing, retry-after-main-
appears logic, and preservation of existing/malformed non-main files.

Closes openclaw#58289
@rogerdigital
rogerdigital force-pushed the fix/non-main-agent-models-json-bootstrap branch from eaf826c to 02b6411 Compare April 9, 2026 04:36

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 02b6411e2e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +194 to +195
if (existingModelsFile.readError) {
throw existingModelsFile.readError;

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.

P2 Badge Defer unreadable models.json errors until after write planning

ensureOpenClawModelsJson now throws immediately when readExistingModelsFile reports readError, before planOpenClawModelsJson runs. That makes any non-ENOENT/ENOTDIR read failure (for example an unreadable models.json after a permissions drift) fatal even when the plan would be write and could regenerate the file via atomic replace in a writable parent directory. Before this commit those cases could recover by rewriting models.json; this change regresses that recovery path outside the intended non-main skip-bootstrap handling.

Useful? React with 👍 / 👎.

@rogerdigital

Copy link
Copy Markdown
Contributor Author

Closing: upstream has been actively refactoring this module (cacheable field removed, MODELS_JSON_STATE introduced, function renames), making it difficult to keep this patch in sync. Will reopen once issue #58289 receives maintainer triage and the models-config churn settles.

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

Labels

agents Agent runtime and tooling size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Non-main agent may miss models.json and fail with "Unknown model" when provider plan is empty

2 participants