Skip to content

fix(update): classify unreadable plugin manifests separately from parse and migration failures#110200

Merged
jalehman merged 4 commits into
openclaw:mainfrom
VACInc:fix/plugin-payload-unreadable-classification-109833
Jul 17, 2026
Merged

fix(update): classify unreadable plugin manifests separately from parse and migration failures#110200
jalehman merged 4 commits into
openclaw:mainfrom
VACInc:fix/plugin-payload-unreadable-classification-109833

Conversation

@VACInc

@VACInc VACInc commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Issue #109833: an unreadable (e.g. root-owned, mode-600) plugin package.json blocks the entire gateway with a misleading diagnosis. Three defects compound:

  1. readPackagePayloadManifest lumps EACCES/EPERM/IO read failures into the same catch as JSON parse errors, reporting invalid-package-json / "Could not parse package.json: EACCES…" for a file that was never read.
  2. The failure is funneled through the startup-migration headline — "OpenClaw startup migrations did not complete cleanly; refusing to report the gateway ready." — although no migration failed.
  3. The guidance recommends openclaw update repair, which cannot fix file ownership/permissions.

Why This Change Was Made

Maintainer-instructed mechanical scope only:

  • src/cli/update-cli/plugin-payload-validation.ts: permission/IO read failures now classify as a distinct unreadable-package-json reason instead of invalid-package-json.
  • src/commands/doctor-config-preflight.ts: plugin-verification blockers get their own accurate headline ("OpenClaw plugin verification failed; refusing to report the gateway ready.") while genuine state-migration failures keep the existing migration wording. Precedence and fail-closed behavior are unchanged (migration warnings → invalid config → plugin convergence, exactly as before).
  • src/cli/update-cli/post-core-plugin-convergence.ts: permission failures now point at the offending path with ownership/permission remediation instead of openclaw update repair.

Explicitly out of scope: whether a configured/enabled plugin with an unreadable payload should degrade-and-boot instead of fail-closed. That is a pending product decision (ClawSweeper needs-product-decision); current deliberate fail-closed behavior is preserved and deferred to the plugin-surface owner.

User Impact

Operators hitting an unreadable plugin file during upgrade now see the true cause (permission failure on a named path) with actionable remediation, instead of a false "migration failure" pointing at repair steps that cannot work. No behavior change for whether startup blocks.

Evidence

Real behavior proof

Environment: Linux, Node 24, built CLI from this worktree. The real run used PR head 8b1c51f1921 after merging origin/main at 9c2d38a596e. Before publishing this evidence, the branch was refreshed again to current origin/main (781913025b6) at final PR head 28a64a12325; the intervening main commits did not touch any of this PR's six files, and the focused tests were rerun on that final head. Two fresh temporary state roots used the same minimal configured plugin, a canonical tracked install record, and plugins.entries.proof-unreadable-109833.enabled=true. The plugin package.json was made unreadable by the same non-root user that launched OpenClaw. Paths and the local username/group are redacted below.

For the before run, the three changed production files were restored to byte-exact current-main contents (git diff --exit-code origin/main -- <three production files> produced no output), then the CLI was built and launched:

$ stat -c '%A %a %U:%G %n' <MAIN_STATE>/staged/proof-unreadable-109833/package.json
---------- 0 <user>:<group> <MAIN_STATE>/staged/proof-unreadable-109833/package.json

$ OPENCLAW_STATE_DIR=<MAIN_STATE> OPENCLAW_CONFIG_PATH=<MAIN_STATE>/openclaw.json \
    node openclaw.mjs gateway run --port <PORT> --auth token --bind loopback
[openclaw] Could not start the CLI.
[openclaw] Reason: OpenClaw startup migrations did not complete cleanly; refusing to report the gateway ready.
- Plugin "proof-unreadable-109833" failed post-core payload smoke check (invalid-package-json): Could not parse package.json: EACCES: permission denied, open '<MAIN_STATE>/staged/proof-unreadable-109833/package.json' Run `openclaw update repair` to retry plugin repair. Run `openclaw plugins inspect proof-unreadable-109833 --runtime --json` for details.
Run "openclaw doctor --fix" against the mounted state/config, then restart the container.
EXIT=1

The branch sources were restored (git diff --exit-code HEAD -- <three production files> produced no output), a fresh forced CLI build completed, and the same scenario ran against a second fresh state root:

$ stat -c '%A %a %U:%G %n' <BRANCH_STATE>/staged/proof-unreadable-109833/package.json
---------- 0 <user>:<group> <BRANCH_STATE>/staged/proof-unreadable-109833/package.json

$ OPENCLAW_STATE_DIR=<BRANCH_STATE> OPENCLAW_CONFIG_PATH=<BRANCH_STATE>/openclaw.json \
    node openclaw.mjs gateway run --port <PORT> --auth token --bind loopback
[openclaw] Could not start the CLI.
[openclaw] Reason: OpenClaw plugin verification failed; refusing to report the gateway ready.
- Plugin "proof-unreadable-109833" failed post-core payload smoke check (unreadable-package-json): Could not read package.json at <BRANCH_STATE>/staged/proof-unreadable-109833/package.json: EACCES: permission denied, open '<BRANCH_STATE>/staged/proof-unreadable-109833/package.json' Fix file access for <BRANCH_STATE>/staged/proof-unreadable-109833/package.json so it is readable by the user running OpenClaw. For EACCES or EPERM, correct its ownership or permissions; otherwise resolve the reported filesystem I/O error, then retry. Run `openclaw plugins inspect proof-unreadable-109833 --runtime --json` for details.
Resolve the plugin verification errors above, then restart the container.
EXIT=1

Both invocations exited 1 before readiness; a listener check returned NO_GATEWAY_LISTENER_ON_29187_OR_29188. This proves the diagnostic changed while fail-closed behavior did not. The temporary fixture, config, state databases, installed payloads, and mode-000 files were removed afterward (TEMP_PROOF_STATE_REMOVED).

Verification

  • Refreshed-head focused test command: OPENCLAW_HEAVY_CHECK_LOCK_SCOPE=worktree node scripts/run-vitest.mjs src/cli/update-cli/plugin-payload-validation.test.ts src/cli/update-cli/post-core-plugin-convergence.test.ts src/commands/doctor-config-preflight.state-migration.test.ts
  • Result on final refreshed head: 3 files / 77 tests passed across 2 Vitest shards in 3.64s.
  • OPENCLAW_FORCE_BUILD=1 pnpm dev ... completed separately for the byte-exact current-main and restored branch source trees, supplying the built CLIs used for the two runs.
  • git diff --check and git diff origin/main...HEAD --check passed.
  • The original code commit passed the mandatory fresh autoreview --mode uncommitted with no actionable findings. This feedback pass changed no code; it only merged current main, reran proof, and updated PR evidence.

Upgrade compatibility and scope

The stored-data-model flag is a false positive from touching startup/doctor and plugin-record consumers: this patch changes only transient discriminated-union values and rendered diagnostics. It does not change config shape, installed-plugin-index serialization, SQLite schema/version, migration inputs/outputs, or persisted state. The same existing state/record shape was accepted by both the current-main and branch runs, so no migration or backfill is required.

Not tested: a live Windows EPERM setup or a self-update that mutates the installed OpenClaw package. The real Linux startup run exercises the shared post-core convergence path with an actual EACCES; focused tests cover both startup headline separation and update-path outcome coherence. The configured-plugin fatality question is intentionally not changed or tested as a new behavior; it remains deferred to the plugin-surface owner.

Refs #109833.

Maintainer Crabbox proof (exact refreshed head)

A separate maintainer-run before/after proof exercised the real built gateway startup path on fresh sanitized direct-AWS Crabbox leases. The PR run was bound to exact head 28a64a123254ca6e101914a2b4de4374b0302adc; the head was rechecked unchanged after the run.

  • Current-main baseline: a9dc393f7eb84f515994789b8eefafa206e96914, AWS lease cbx_51173939c1a3, run run_f62039878a59.
  • PR head: 28a64a123254ca6e101914a2b4de4374b0302adc, AWS lease cbx_244518ab556e, run run_c9c0cacb919d.
  • Isolation: --fresh-pr openclaw/openclaw#110200, --no-hydrate, CRABBOX_ENV_ALLOW=CI, public networking, no Tailscale state, empty AWS instance profile, and trusted scripts/crabbox-untrusted-bootstrap.sh.
  • Fixture: isolated temporary HOME/state/config; canonical tracked plugin install record; plugin configured in plugins.allow; package.json owned by root:root with mode 0600; gateway launched as unprivileged user crabbox.
  • Behavior command: build, then node dist/entry.js gateway run.

Redacted current-main result:

GATEWAY_EXIT=1
[openclaw] Reason: OpenClaw startup migrations did not complete cleanly;
refusing to report the gateway ready.
- Plugin "proof-plugin" failed post-core payload smoke check
  (invalid-package-json): Could not parse package.json:
  EACCES: permission denied, open
  '$TMP/state/extensions/proof-plugin/package.json'
  Run `openclaw update repair` to retry plugin repair.

Redacted PR-head result:

GATEWAY_EXIT=1
[openclaw] Reason: OpenClaw plugin verification failed;
refusing to report the gateway ready.
- Plugin "proof-plugin" failed post-core payload smoke check
  (unreadable-package-json): Could not read package.json at
  $TMP/state/extensions/proof-plugin/package.json:
  EACCES: permission denied
  Fix file access for $TMP/state/extensions/proof-plugin/package.json
  so it is readable by the user running OpenClaw. For EACCES or EPERM,
  correct its ownership or permissions...
PROOF_ASSERTIONS=passed

All four target claims passed: the failure is classified as unreadable-package-json; the top-level error identifies plugin verification rather than startup migration; the message names the affected path and gives ownership/permission remediation without update-repair advice; and startup remains deliberately fail-closed with exit 1.

Supporting focused tests passed: 3 files / 77 tests across plugin-payload-validation.test.ts, post-core-plugin-convergence.test.ts, and doctor-config-preflight.state-migration.test.ts.

Setup limitation: the canonical installed-plugin record was seeded with OpenClaw's storage helper to reproduce the already-installed/corrupted state deterministically. The observed behavior used the real built CLI, real Linux file permissions, and the unprivileged gateway user; it did not add a systemd or Docker service-manager layer.

Both task-owned Crabbox leases were released after proof.

@openclaw-barnacle openclaw-barnacle Bot added cli CLI command changes commands Command implementations size: S labels Jul 17, 2026
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. labels Jul 17, 2026
@clawsweeper

clawsweeper Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 17, 2026, 7:30 PM ET / 23:30 UTC.

Summary
The branch adds an unreadable-manifest failure classification, permission-specific remediation, and a distinct startup plugin-verification refusal headline with focused regression coverage.

PR surface: Source +55, Tests +78. Total +133 across 6 files.

Reproducibility: yes. The supplied real Linux path creates an installed plugin with an unreadable package.json, launches the built gateway as an unprivileged user, and observes the startup refusal; the changed source directly owns classification and formatting of that result.

Review metrics: none identified.

Stored data model
Persistent data-model change detected: migration/backfill/repair: src/commands/doctor-config-preflight.ts, serialized state: src/cli/update-cli/plugin-payload-validation.test.ts, serialized state: src/cli/update-cli/plugin-payload-validation.ts, unknown-data-model-change: src/commands/doctor-config-preflight.ts. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #109833
Summary: This PR is a candidate fix for the diagnostic and remediation portion of the canonical unreadable-plugin startup report; the report separately retains the product question of whether startup should degrade rather than fail closed.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Obtain normal maintainer approval after reviewing the current merge result against main.

Risk before merge

  • [P1] The branch is behind current main, so the maintainer should review the current three-way merge result before landing; this is a review-refresh concern, not evidence that the patch deletes current-main behavior.

Maintainer options:

  1. Decide the mitigation before merge
    Keep the diagnostic-only scope, review the merge result against current main, and merge if the existing fail-closed plugin-verification policy remains the intended behavior; leave the separate degrade-and-boot decision with the canonical report.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P1] No mechanical repair is indicated; a maintainer should review the refreshed merge result and confirm the retained fail-closed policy boundary.

Maintainer decision needed

  • Question: Should this proven diagnostic-only change be approved after a maintainer checks its clean merge result against current main and confirms that configured unreadable plugins should continue to block readiness?
  • Rationale: The patch deliberately preserves a fail-closed startup policy that the linked report identifies as a separate product choice; normal maintainer approval is needed for that retained policy boundary and the branch is behind main.
  • Likely owner: jalehman — They are the only concrete reviewer-routing signal in the supplied PR timeline.
  • Options:
    • Approve retained fail-closed scope (recommended): Merge the patch after confirming the current merge result keeps plugin-verification failures fail-closed and only improves their classification and remediation.
    • Broaden the product decision first: Pause this PR only if maintainers want unreadable configured plugins to degrade and boot instead, then define that behavior in the canonical report before changing this diagnostic patch.

Security
Cleared: The patch changes local error classification, rendered guidance, and tests only; no dependency, workflow, permission, secret, or supply-chain surface is introduced.

Review details

Best possible solution:

Keep the diagnostic-only scope, review the merge result against current main, and merge if the existing fail-closed plugin-verification policy remains the intended behavior; leave the separate degrade-and-boot decision with the canonical report.

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

Yes. The supplied real Linux path creates an installed plugin with an unreadable package.json, launches the built gateway as an unprivileged user, and observes the startup refusal; the changed source directly owns classification and formatting of that result.

Is this the best way to solve the issue?

Yes for the stated defect. Separating filesystem read errors from JSON parse errors and preserving the existing fail-closed behavior is the narrowest maintainable correction; changing whether the gateway boots is a separate policy decision.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against b42dc728510b.

Label changes

Label justifications:

  • P1: The affected gateway startup path can block operators during an upgrade, while this PR supplies a narrow correction to the reported diagnosis and remediation.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR provides redacted before/after real built-gateway output using actual Linux file permissions and an unprivileged runtime user, demonstrating corrected diagnostics while preserving exit-1 fail-closed behavior.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR provides redacted before/after real built-gateway output using actual Linux file permissions and an unprivileged runtime user, demonstrating corrected diagnostics while preserving exit-1 fail-closed behavior.
Evidence reviewed

PR surface:

Source +55, Tests +78. Total +133 across 6 files.

View PR surface stats
Area Files Added Removed Net
Source 3 68 13 +55
Tests 3 81 3 +78
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 6 149 16 +133

What I checked:

  • Distinct read-failure classification: The patch reads package.json before parsing it, maps read failures to unreadable-package-json, and retains invalid-package-json only for parsing failures. (src/cli/update-cli/plugin-payload-validation.ts:134, 28a64a123254)
  • Accurate startup boundary: Plugin convergence warnings are returned as a plugin-verification diagnostic and formatted under a dedicated readiness-refusal headline, while migration failures continue to use their migration-specific formatter. (src/commands/doctor-config-preflight.ts:161, 28a64a123254)
  • Actionable operator guidance: Unreadable package.json failures direct operators to repair ownership, permissions, or filesystem I/O at the named plugin path instead of suggesting update repair. (src/cli/update-cli/post-core-plugin-convergence.ts:50, 28a64a123254)
  • Focused regression coverage: The branch adds EACCES, EPERM, and EIO classification coverage plus convergence-guidance and startup-headline assertions; the supplied evidence reports 77 passing focused tests. (src/cli/update-cli/plugin-payload-validation.test.ts:620, 28a64a123254)
  • Real behavior proof: The PR body records a built, unprivileged Linux gateway run against a mode-0600 root-owned package.json before and after the patch: both runs refuse readiness with exit 1, while the branch changes the misleading migration diagnosis and repair advice to plugin-verification and permission remediation. (28a64a123254)
  • Canonical report relationship: The linked report describes the same unreadable plugin package.json startup diagnosis; this branch implements its explicit classification-and-remediation alternative while deliberately leaving the separate degrade-versus-fail-closed policy question unchanged.

Likely related people:

  • jalehman: The timeline shows jalehman assigned themself after the PR was opened, making them the clearest available routing contact for this narrow startup/plugin-verification review. (role: assigned reviewer; confidence: medium; files: src/commands/doctor-config-preflight.ts, src/cli/update-cli/plugin-payload-validation.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (2 earlier review cycles)
  • reviewed 2026-07-17T22:12:46.961Z sha ce6e200 :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-17T23:03:43.413Z sha 28a64a1 :: needs maintainer review before merge. :: none

@jalehman jalehman self-assigned this Jul 17, 2026
@VACInc

VACInc commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 17, 2026
@jalehman
jalehman merged commit 748fc04 into openclaw:main Jul 17, 2026
147 of 159 checks passed
@jalehman

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 18, 2026
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes commands Command implementations P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants