Skip to content

Models/Secrets: harden SecretRef-managed models.json persistence and audit#38470

Closed
joshavant wants to merge 45 commits intomainfrom
feat/secretref-models-persistence-hardening
Closed

Models/Secrets: harden SecretRef-managed models.json persistence and audit#38470
joshavant wants to merge 45 commits intomainfrom
feat/secretref-models-persistence-hardening

Conversation

@joshavant
Copy link
Copy Markdown
Contributor

Summary

This PR hardens the models.json + SecretRef surface end-to-end so SecretRef-managed provider credentials are never persisted as resolved secrets in generated agents/*/agent/models.json, while preserving existing plaintext behavior for valid non-SecretRef setups.

It also closes nearby gaps around provider header SecretRefs, models list command-path persistence behavior, and secrets audit visibility for generated models.json residues.

Aggregated Problem Surface Addressed

  • Resolved provider auth values could be written to agents/*/agent/models.json for SecretRef-managed providers.
  • models list command paths could persist from resolved runtime config instead of source config.
  • Merge mode could preserve stale apiKey values even when provider auth became SecretRef-managed.
  • Non-env SecretRefs needed a safe non-secret persisted marker for required apiKey fields.
  • models.providers.*.headers.* SecretRef surfaces lacked full runtime/audit/persistence handling.
  • secrets audit did not fully cover generated models.json provider auth/header residues.
  • models.json write paths needed stronger serialization and mode hardening.

What Changed

  • Added runtime source-snapshot plumbing and used source config for models command persistence paths.
  • Implemented provenance-based provider auth persistence (env ref, non-env ref, plaintext) instead of pattern-only logic.
  • Added/standardized non-secret markers (secretref-managed, secretref-env:*, OAuth/local markers) and marker-aware handling.
  • Updated merge behavior: preserve existing apiKey only for providers not SecretRef-managed in current config/auth-profile context.
  • Added models.providers.*.headers.* SecretRef support in collectors/registry/runtime sanitation/audit.
  • Hardened models.json writes with per-path serialization and file mode enforcement (0600) on write and no-content-change paths.
  • Extended secrets audit to inspect generated agents/*/agent/models.json for unresolved SecretRef objects and plaintext-sensitive residues.
  • Updated docs/schema help to reflect actual merge and marker semantics.

Behavioral Guarantees Preserved

  • Startup still fails closed when effectively active SecretRefs are unresolved.
  • Reload still keeps last-known-good runtime snapshot on resolution failure.
  • Plaintext-first valid configurations keep existing behavior.
  • External secrets management remains fully opt-in.

Related Cluster (Issues/PRs)

This PR relates to the same cluster identified in the audit report:

Validation

  • Local gates:
    • pnpm check
    • pnpm build
  • Targeted tests across touched surfaces:
    • models persistence + merge + auth provenance suites
    • command-path source-snapshot suites
    • provider header marker/sanitization suites
    • secrets runtime/audit/apply/plan suites
    • gateway startup/reload config suites
  • VM smoke matrix (post-rebase):
    • env SecretRef marker persistence
    • non-env SecretRef marker persistence
    • plaintext compatibility path
    • startup hard-fail on unresolved SecretRef
    • reload failure keeps process healthy (last-known-good behavior)

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 7, 2026

🔒 Aisle Security Analysis

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

# Severity Title
1 🟡 Medium Secrets audit may read models.json outside stateDir via configurable agentDir (unrestricted file access + potential DoS)
2 🟡 Medium Symlink/Hardlink following allows arbitrary file overwrite/chmod when writing models.json
3 🔵 Low Secrets audit may miss plaintext secrets in models.json due to overly broad env-var marker detection

1. 🟡 Secrets audit may read models.json outside stateDir via configurable agentDir (unrestricted file access + potential DoS)

Property Value
Severity Medium
CWE CWE-73
Location src/secrets/storage-scan.ts:81-88

Description

runSecretsAudit() now scans models.json files discovered by listAgentModelsJsonPaths(). For agent IDs coming from config, it uses resolveAgentDir(config, agentId) which accepts agents.list[].agentDir from config and resolves it to an absolute path (via resolveUserPath) without constraining it to the OpenClaw stateDir.

As a result:

  • Unintended file access: If a config sets agentDir to an arbitrary location, the audit will attempt to read <agentDir>/models.json even if it is outside the intended state directory.
    • This can be abused when an attacker can influence the config and filesystem (e.g., place a symlink at <agentDir>/models.json pointing to another file). The audit will follow the symlink and read the target.
  • Denial-of-service risk: readJsonObjectIfExists() uses fs.readFileSync() and JSON.parse() with no file-size/type checks. A very large file or special file at that path can cause high memory/CPU usage or blocking reads.
  • Information disclosure in reports: Findings include the full file path for the scanned file, which can leak sensitive host path information in audit output/logs.

Vulnerable code (path construction from config-controlled agentDir):

const agentDir = resolveAgentDir(config, agentId);
paths.add(path.join(resolveUserPath(agentDir), "models.json"));

Sinks:

const raw = fs.readFileSync(filePath, "utf8");
const parsed = JSON.parse(raw);

Recommendation

Harden models.json discovery and reading:

  1. Constrain scans to the intended state directory (recommended for an audit tool).
    • Ignore agents.list[].agentDir overrides when scanning for models.json, and instead derive paths strictly from the provided stateDir:
const stateRoot = resolveUserPath(stateDir);
for (const agentId of listAgentIds(config)) {
  const id = normalizeAgentId(agentId);
  paths.add(path.join(stateRoot, "agents", id, "agent", "models.json"));
}
  1. If agentDir overrides must be supported, enforce an allowlist root and resolve symlinks:
const allowedRoot = path.join(resolveUserPath(stateDir), "agents") + path.sep;
const candidate = fs.realpathSync.native(path.join(resolveUserPath(agentDir), "models.json"));
if (!candidate.startsWith(allowedRoot)) return; // skip
  1. Prevent DoS / unsafe file types before reading:
    • lstatSync/statSync to ensure it is a regular file
    • reject symlinks (or require realpath checks)
    • cap file size (e.g., 1–5 MB) before readFileSync
const st = fs.statSync(candidate);
if (!st.isFile() || st.size > 5 * 1024 * 1024) return;
  1. Consider redacting or shortening scanned paths in user-facing audit output (or gate full paths behind a --verbose flag).

2. 🟡 Symlink/Hardlink following allows arbitrary file overwrite/chmod when writing models.json

Property Value
Severity Medium
CWE CWE-59
Location src/agents/models-config.ts:216-303

Description

The ensureOpenClawModelsJson() routine writes models.json and enforces permissions using path-based filesystem operations (fs.writeFile and fs.chmod) without defending against symlink/hardlink attacks.

Key points:

  • Attacker-controlled path: the target directory can be influenced via:
    • OPENCLAW_AGENT_DIR / PI_CODING_AGENT_DIR (see resolveOpenClawAgentDir()), and
    • direct agentDirOverride passed by various callers.
  • Symlink following: Node’s fs.writeFile(path, ...) and fs.chmod(path, ...) follow symlinks by default.
  • TOCTOU window: the code does readRawFile(targetPath) and later writeFile(targetPath, ...) / chmod(targetPath, ...). An attacker who can write inside agentDir can replace models.json with a symlink (or a hardlink where allowed) between these operations.
  • Impact: if OpenClaw runs with elevated privileges (or is pointed at a directory writable by an untrusted user), this can be used to:
    • overwrite arbitrary files with JSON content, and/or
    • change permissions of arbitrary files to 0600 (including on the “no content change” path).

Vulnerable code:

await fs.writeFile(targetPath, next, { mode: 0o600 });
await ensureModelsFileMode(targetPath);// ...
async function ensureModelsFileMode(pathname: string): Promise<void> {
  await fs.chmod(pathname, 0o600).catch(() => {// best-effort
  });
}

Recommendation

Harden file writes/permission changes against symlink/hardlink attacks and make the write atomic.

Recommended pattern:

  1. Ensure agentDir exists with correct permissions.
  2. Write to a random temp file in the same directory, set mode on the temp file (via fchmod if possible), then rename() it over models.json.
  3. For the “no content change” path, avoid chmod(path). Instead open the file with O_NOFOLLOW and use fchmod() on the returned FD/handle; also reject non-regular files and (optionally) nlink > 1.

Example (POSIX-oriented):

import { constants as c } from "node:fs";
import { randomUUID } from "node:crypto";

await fs.mkdir(agentDir, { recursive: true, mode: 0o700 });

const tmpPath = path.join(agentDir, `.models.json.${process.pid}.${randomUUID()}.tmp`);
const mode = 0o600;// Write temp with O_EXCL + (when supported) O_NOFOLLOW
const flags = c.O_WRONLY | c.O_CREAT | c.O_EXCL | (c.O_NOFOLLOW ?? 0);
const handle = await fs.open(tmpPath, flags, mode);
try {
  await handle.writeFile(next, "utf8");
  await handle.chmod(mode).catch(() => {});
} finally {
  await handle.close();
}// rename replaces an existing symlink *itself* rather than following it
await fs.rename(tmpPath, targetPath);

Alternatively, reuse existing repository helpers such as writeTextAtomic() / writeJsonAtomic() (from src/infra/json-files.ts) and add a safe-mode repair path that uses open(..., O_NOFOLLOW) + fchmod rather than chmod(path).

Also consider explicitly refusing to operate when lstat(targetPath).isSymbolicLink() or stat.nlink > 1 (hardlink) is detected, and avoid best-effort chmod on paths that cannot be verified safely.


3. 🔵 Secrets audit may miss plaintext secrets in models.json due to overly broad env-var marker detection

Property Value
Severity Low
CWE CWE-807
Location src/secrets/audit.ts:401-435

Description

secrets audit now scans generated agents/*/agent/models.json for plaintext provider credentials, but it suppresses findings when values look like markers.

The new marker logic treats any ALL-CAPS/underscore/digit string (/^[A-Z][A-Z0-9_]*$/) as an env-var marker by default. In collectModelsJsonSecrets(), this causes a false negative when a real API key/token happens to match that pattern (e.g., base32/uppercase secrets, or other providers’ uppercase credential formats):

  • Input: providers.<id>.apiKey read from models.json (untrusted file content at rest)
  • Decision point: isNonSecretApiKeyMarker(apiKey) returns true for env-var-shaped strings
  • Effect: audit skips PLAINTEXT_FOUND, leaving a plaintext secret at rest undetected

Additionally, header values are skipped when they start with secretref-env: without validating that the suffix is a legitimate env-var name, which can also suppress findings for malformed/abused values.

Vulnerable logic:

} else if (isNonEmptyString(apiKey) && !isNonSecretApiKeyMarker(apiKey)) {
  addFinding(... "PLAINTEXT_FOUND" ...)
}
...
if (isSecretRefHeaderValueMarker(headerValue)) {
  continue;
}

Recommendation

Make audit’s suppression rules strict and evidence-based, so it avoids intended markers without creating blind spots:

  1. Do not treat arbitrary env-var-shaped strings as non-secret markers during audit. Instead, only suppress when the value is:

    • A known fixed marker token (e.g. minimax-oauth, qwen-oauth, ollama-local, secretref-managed, AWS SDK marker names), or
    • An env var name that is known to be used as a SecretRef (derived from config/auth profile refs), or optionally present in .env.
  2. Validate secretref-env: header markers by ensuring the suffix is a valid env var name (e.g. isValidEnvSecretRefId()), otherwise treat it as plaintext.

Example hardening (illustrative):

import { isValidEnvSecretRefId } from "../config/types.secrets.js";
import { isEnvVarNameMarker, isAwsSdkAuthMarker, NON_ENV_SECRETREF_MARKER } from "../agents/model-auth-markers.js";

function isAuditSafeMarkerApiKey(value: string, knownEnvRefIds: Set<string>): boolean {
  const v = value.trim();
  if (!v) return false;// Only fixed tokens / known markers
  if (v === NON_ENV_SECRETREF_MARKER || isAwsSdkAuthMarker(v) || v === "minimax-oauth" || v === "qwen-oauth" || v === "ollama-local") {
    return true;
  }// Only treat env var name as marker if it is actually referenced
  return isEnvVarNameMarker(v) && knownEnvRefIds.has(v);
}

function isAuditSafeSecretRefEnvHeaderMarker(value: string): boolean {
  const v = value.trim();
  if (!v.startsWith("secretref-env:")) return false;
  const suffix = v.slice("secretref-env:".length);
  return isValidEnvSecretRefId(suffix);
}

If you want to preserve the current UX (storing env var names in models.json), consider adding a disambiguating prefix for env-var markers (e.g. $OPENAI_API_KEY), which removes collision with uppercase secrets.


Analyzed PR: #38470 at commit 4e0dfa1

Last updated on: 2026-03-07T03:11:52Z

@openclaw-barnacle openclaw-barnacle bot added docs Improvements or additions to documentation gateway Gateway runtime commands Command implementations agents Agent runtime and tooling size: XL maintainer Maintainer-authored PR labels Mar 7, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

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: 4e0dfa16fb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +82 to +84
if (agentId === "main") {
paths.add(path.join(resolveUserPath(stateDir), "agents", "main", "agent", "models.json"));
continue;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scan custom main agentDir models.json

When agents.list config defines id: "main" with a custom agentDir, this branch still hard-codes the default stateDir/agents/main/agent/models.json path and never calls resolveAgentDir for main. As a result, runSecretsAudit can miss plaintext or unresolved secrets in the real main agent models.json whenever that directory is outside the default state tree, creating a false “clean” audit result for exactly the new models.json checks added in this commit.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 7, 2026

Greptile Summary

This PR systematically hardens the SecretRef-to-models.json persistence pipeline across 53 files: non-env SecretRef values are now replaced with non-secret marker strings before being written to disk, env-SecretRef providers store an env-var-name reference instead, and the secrets audit command gains a new scan of generated models.json files. Supporting changes include per-path write locking for models.json, a source-config snapshot plumbing path (so the resolved runtime config is never used as the persistence input), and header-level SecretRef support in collectors and runtime sanitization.

Key areas of the change:

  • src/agents/model-auth-markers.ts – new module centralising all non-secret marker constants and predicates used across persistence, merge, runtime, and audit paths.
  • src/agents/models-config.ts – write path overhauled: normalize-before-merge ordering, per-path mutex (withModelsJsonWriteLock), 0600 file-mode hardening on both write and no-change paths, and source-snapshot routing via resolveModelsConfigInput.
  • src/agents/models-config.providers.tsresolveApiKeyFromProfiles now returns a typed provenance record (ProfileApiKeyResolution) enabling downstream callers to distinguish plaintext from env/non-env SecretRef sources; header SecretRef normalization added.
  • src/secrets/audit.tscollectModelsJsonSecrets added, covering unresolved SecretRef objects and plaintext residues in provider apiKey and sensitive header fields.
  • One logic gap found: in mergeWithExistingProviderSecrets (models-config.ts ~line 168), the guard !secretRefManagedProviders.has(key) correctly skips preservation for providers that are currently SecretRef-managed, but does not prevent a stale NON_ENV_SECRETREF_MARKER string already present in the file from being preserved when a provider transitions from SecretRef-managed to plaintext. The stale marker would override the new plaintext value and cause runtime auth failures. See the inline comment for a suggested fix using isNonSecretApiKeyMarker.

Confidence Score: 3/5

  • Safe to merge for new SecretRef configurations, but the SecretRef-to-plaintext transition edge case can cause runtime auth failures in merge mode until fixed.
  • The broad set of changes is well-structured and the core SecretRef-hardening goals are met for all new and steady-state configurations. However, the merge logic in mergeWithExistingProviderSecrets does not guard against preserving a stale non-env-ref marker from an existing models.json when the user switches a provider back to plaintext credentials. Any user who previously had a non-env SecretRef provider and now removes the SecretRef from their config would silently receive broken API credentials on the next models.json regeneration in merge mode. The issue is localised to one predicate check and would not affect plain-plaintext or brand-new SecretRef deployments.
  • src/agents/models-config.ts — specifically the mergeWithExistingProviderSecrets function around the preserved.apiKey assignment.

Last reviewed commit: 4e0dfa1

Comment on lines 168 to 175
const preserved: Record<string, unknown> = {};
if (typeof existing.apiKey === "string" && existing.apiKey) {
if (
!secretRefManagedProviders.has(key) &&
typeof existing.apiKey === "string" &&
existing.apiKey
) {
preserved.apiKey = existing.apiKey;
}
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.

Stale non-env SecretRef marker preserved after transitioning to plaintext

When a provider previously had a non-env SecretRef (causing NON_ENV_SECRETREF_MARKER to be written into models.json), and the user then switches to a plaintext credential in their config, this merge step incorrectly preserves the stale marker.

Since secretRefManagedProviders does not include the provider in the new plaintext case, !secretRefManagedProviders.has(key) evaluates to true. preserved.apiKey gets set to the stale marker value from the file, and the { ...newEntry, ...preserved } spread on line 179 lets it override the freshly-normalized plaintext value written into newEntry.

At runtime, the provider ends up with an unresolvable marker string as its API credential, causing authentication failures.

The fix is to add a guard that skips preservation when the existing stored value is itself a non-secret marker. isNonSecretApiKeyMarker(existing.apiKey, { includeEnvVarName: false }) from model-auth-markers.ts can serve this purpose — passing includeEnvVarName: false preserves env-var-name references (all-caps identifiers like PROVIDER_ENV_VAR) while blocking OAuth/local/non-env-ref marker strings from surviving the credential-type transition.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/models-config.ts
Line: 168-175

Comment:
**Stale non-env SecretRef marker preserved after transitioning to plaintext**

When a provider previously had a non-env SecretRef (causing `NON_ENV_SECRETREF_MARKER` to be written into `models.json`), and the user then switches to a plaintext credential in their config, this merge step incorrectly preserves the stale marker.

Since `secretRefManagedProviders` does not include the provider in the new plaintext case, `!secretRefManagedProviders.has(key)` evaluates to `true`. `preserved.apiKey` gets set to the stale marker value from the file, and the `{ ...newEntry, ...preserved }` spread on line 179 lets it override the freshly-normalized plaintext value written into `newEntry`.

At runtime, the provider ends up with an unresolvable marker string as its API credential, causing authentication failures.

The fix is to add a guard that skips preservation when the existing stored value is itself a non-secret marker. `isNonSecretApiKeyMarker(existing.apiKey, { includeEnvVarName: false })` from `model-auth-markers.ts` can serve this purpose — passing `includeEnvVarName: false` preserves env-var-name references (all-caps identifiers like `PROVIDER_ENV_VAR`) while blocking OAuth/local/non-env-ref marker strings from surviving the credential-type transition.

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

@joshavant joshavant marked this pull request as draft March 7, 2026 05:14
vincentkoc and others added 14 commits March 7, 2026 09:09
…al routes (#38418)

* fix(gateway): prevent webchat messages from cross-routing to external channels

chat.send always originates from the webchat/control-UI surface.  Previously,
channel-scoped session keys (e.g. agent:main:slack:direct:U…) caused
OriginatingChannel to inherit the session's stored external route, so the
reply dispatcher would route responses to Slack/Telegram instead of back to
the gateway connection.  Remove the route-inheritance logic from chat.send and
always set OriginatingChannel to INTERNAL_MESSAGE_CHANNEL ("webchat").

Closes #34647

Made-with: Cursor

* Gateway: preserve configured-main connect gating

* Gateway: cover connect-without-client routing

* Gateway: add chat.send session key length limit

* Gateway: cap chat.send session key schema

* Gateway: bound chat.send session key parsing

* Gateway: cover oversized chat.send session keys

* Update CHANGELOG.md

---------

Co-authored-by: SidQin-cyber <[email protected]>
* Config: add media retention TTL setting

* Media: recurse persisted media cleanup

* Gateway: add persisted media cleanup timer

* Media: harden retention cleanup sweep

* Media: make recursive retention cleanup opt-in

* Media: retry writes after empty-dir cleanup race
…th (#38492)

* fix(googlechat): inherit shared defaults from accounts.default

* fix(googlechat): do not inherit default enabled state

* fix(googlechat): avoid inheriting default credentials

* fix(googlechat): keep dangerous auth flags account-local
…etwork errors (#38530)

Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: xinhuagu <[email protected]>
Co-authored-by: Tak Hoffman <[email protected]>
* Web: add HEIC media normalization regression

* Docs: list HEIC input_image MIME types

* Update src/web/media.test.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
…38422)

Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: xinhuagu <[email protected]>
Co-authored-by: Tak Hoffman <[email protected]>
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: yfge <[email protected]>
Co-authored-by: Tak Hoffman <[email protected]>
@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

2 similar comments
@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle openclaw-barnacle bot closed this Mar 7, 2026
@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

24 similar comments
@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

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

Labels

agents Agent runtime and tooling app: android App: android app: ios App: ios app: macos App: macos app: web-ui App: web-ui channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: feishu Channel integration: feishu channel: googlechat Channel integration: googlechat channel: imessage Channel integration: imessage channel: irc channel: line Channel integration: line channel: matrix Channel integration: matrix channel: mattermost Channel integration: mattermost channel: msteams Channel integration: msteams channel: nextcloud-talk Channel integration: nextcloud-talk channel: nostr Channel integration: nostr channel: signal Channel integration: signal channel: slack Channel integration: slack channel: telegram Channel integration: telegram channel: tlon Channel integration: tlon channel: twitch Channel integration: twitch channel: voice-call Channel integration: voice-call channel: whatsapp-web Channel integration: whatsapp-web channel: zalo Channel integration: zalo channel: zalouser Channel integration: zalouser cli CLI command changes commands Command implementations docs Improvements or additions to documentation extensions: acpx extensions: copilot-proxy Extension: copilot-proxy extensions: diagnostics-otel Extension: diagnostics-otel extensions: google-gemini-cli-auth Extension: google-gemini-cli-auth extensions: llm-task Extension: llm-task extensions: lobster Extension: lobster extensions: memory-core Extension: memory-core extensions: memory-lancedb Extension: memory-lancedb extensions: minimax-portal-auth extensions: open-prose Extension: open-prose extensions: qwen-portal-auth Extension: qwen-portal-auth gateway Gateway runtime maintainer Maintainer-authored PR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants