Skip to content

feat(memory-plugin): add ov-memory-doctor skill and diagnostics script for Claude Code and Codex - #4389

Merged
ZaynJarvis merged 4 commits into
volcengine:mainfrom
t0saki:feat/memory-plugin-doctor-skill
Aug 27, 2026
Merged

feat(memory-plugin): add ov-memory-doctor skill and diagnostics script for Claude Code and Codex#4389
ZaynJarvis merged 4 commits into
volcengine:mainfrom
t0saki:feat/memory-plugin-doctor-skill

Conversation

@t0saki

@t0saki t0saki commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Description

Adds a client-side troubleshooting skill, ov-memory-doctor, to the Claude Code and Codex memory plugins, backed by a scripts/ov-memory-doctor.mjs report script and a shared doctor-core.mjs module.

The plugins fail silently in three places that users cannot see from inside the harness: the install (marketplace registration, enablement, hooks, MCP wiring — when wrong, no hook ever runs and nothing is logged), the client config (a malformed ovcli.conf is indistinguishable from a missing one and silently disables the Claude Code plugin / falls back to http://127.0.0.1:1933 on Codex; a stray OPENVIKING_* env var silently overrides the file), and the connection (GET /health returns 200 even with an invalid key, so the statusline shows green while every real request 401s). The installer performs no URL/API-key validation at all, so a wrong URL or dead key installs "successfully" and only surfaces later as empty recall. The existing /ov command probes /health only and never /mcp.

The doctor script runs the install, configuration, connection, server-health and recent-activity checks in one pass and prints a fix for every finding; the skill tells the agent when to run it, how to map findings to causes, which targeted checks to run when the report is not conclusive, and a set of rules for things that go wrong during troubleshooting itself (never print the key, /health 200 is not auth, ov doctor is server-side, setup.mjs cannot bootstrap on older versions, debug-capture.mjs corrupts the live capture cursor, etc.).

API keys are never printed in full: three-segment keys are shown as account=<decoded> user=<decoded> secret=abcd…wxyz (the first two segments are base64url identity, decoded so the operator can see which account/user the key claims), legacy keys as abcd…wxyz (64 chars).

Human Involvement

  • A human participated in the implementation or review loop
  • This PR was generated entirely by AI agents without human participation in the loop

Related Issue

N/A

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement
  • Test update

Changes Made

  • examples/memory-plugin-shared/lib/doctor-core.mjs (new, synced to Claude Code / Codex via sync.mjs): API key display and shape checks, base URL lint, JSON config inspection that reports parse errors, OPENVIKING_* / proxy / TLS env sweep, the server probe ladder (GET /health unauthenticated → GET /health with credentials for the identity echo → GET /api/v1/system/status for a real 401/403 → GET /api/v1/fs/ls?uri=viking://~/memories for tenant-data authorization and the resolved user space → POST /mcp tools/list) with interpretation, state-file and JSONL hook-log scanning, a Server health section (GET /ready interpreted per subsystem; when the url is loopback also whether anything listens on the port and the plugin-only ov.conf keys — claude_code, codex, server.url — that make openviking-server refuse to start; the docker image's pending_initialization stub is recognised on /health), and report rendering with --json output and exit codes. Everything else server-side (config validation, live embedding probe, native engine, disk) is left to openviking-server doctor.
  • examples/claude-code-memory-plugin/scripts/ov-memory-doctor.mjs (new): install checks against installed_plugins.json, known_marketplaces.json, ~/.claude/settings.json (enablement, legacy merged hooks, statusline path, file-type marketplace, duplicate ids, missing skills/ in a version-keyed cache), rc-file residue, claude plugin list --json; config resolution with per-field sources (mirrors ov-status.mjs rather than trusting loadConfig().configPath), isPluginEnabled verdict with the reason, root_api_key fallback warning, bypass patterns, hook-budget checks; connection probes; ~/.openviking/state, pending queue and cc-hooks.log evidence including MCP proxy url drift.
  • examples/claude-code-memory-plugin/skills/ov-memory-doctor/{SKILL.md,reference.md} (new): trigger description, workflow, finding → cause → action table, targeted curl checks, fixing guidance and rules; reference with paths, resolution chains, auth modes, exact server / proxy error strings, state-file fields and a symptom catalogue. allowed-tools pre-approves the doctor script.
  • examples/codex-memory-plugin/scripts/ov-memory-doctor.mjs (new): same shape for Codex — ~/.codex/config.toml ([features] plugin_hooks, [plugins."…"] enabled, [hooks.state] trust records for the four hooks, legacy sections), codex plugin list/marketplace list --json, cache version vs running copy, credential source mode (OPENVIKING_CREDENTIAL_SOURCE), auth mode vs the server's auth_mode, recall/capture timeouts vs hook budgets, codex-plugin-state orphan sessions and codex-hooks.log.
  • examples/codex-memory-plugin/skills/ov-memory-doctor/{SKILL.md,reference.md} (new): Codex variant (no ${PLUGIN_ROOT} substitution in skill bodies, so the skill locates the script via the plugin cache / codex plugin list --json).
  • examples/codex-memory-plugin/.codex-plugin/plugin.json: declares "skills": "./skills/". Every bundled Codex plugin declares this key and the OpenViking manifest did not, so the existing openviking-memory and ov-experience-memory skills were shipped but most likely never loaded. Version 0.7.6 → 0.7.7.
  • examples/claude-code-memory-plugin/.claude-plugin/plugin.json, package.json: version 0.4.4 → 0.4.5. Both harnesses cache plugins by manifest version, so a new skill cannot reach users without a bump (this machine's cache still lacks the skills/ directory added on 2026-08-17 under an unchanged 0.4.4).
  • examples/memory-plugin-shared/{sync.mjs,sync.test.mjs}: doctor-core.mjs synced only to the two plugins that ship a doctor script (DOCTOR_SHARED_FILES); zcode gets an explicit file list instead of the whole directory.
  • Tests: examples/memory-plugin-shared/doctor-core.test.mjs (key display, URL lint, error classification, JSON inspection, probe assessment, log scanning, report rendering), additions to both marketplace.test.mjs (skill files present, node --check on the scripts, Codex skills key), wired into .github/workflows/pr.yml; .github/scripts/stage-memory-plugin-marketplace.sh requires the new skill and script files.
  • READMEs (Claude Code EN/CN, Codex): Troubleshooting entry point and plugin structure entries.

Testing

  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have tested this on the following platforms:
    • Linux
    • macOS
    • Windows

The full node --test list from .github/workflows/pr.yml passes (351 pass, 1 pre-existing skip). claude plugin validate --strict passes for both plugins.

Both doctor scripts were run live against a real server (ov-dev, auth_mode=api_key) and with injected faults: invalid key (/health 200 without identity + system/status 401 + /mcp 401 all flagged), unreachable port (ECONNREFUSED classified), URL with /api/v1 suffix (lint + /health 404), malformed ovcli.conf (parse error reported, "plugin disabled" verdict with reason), fresh HOME (no config / not installed), three-segment key with a mismatching configured account, Bearer -prefixed key, mangled base64url segment, --json, --offline; server side: loopback server with lsof port owner and /ready ok, dead port, remote server (/ready only), ov.conf with claude_code/codex/server.url blocks, stub servers answering /ready with failing checks, 503 initializing, 404 and the docker pending_initialization stub.

Checklist

  • My code follows the project's coding style
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

Screenshots (if applicable)

N/A

Additional Notes

  • Related defects found during this work are fixed separately in fix(memory-plugin): setup wizard first-run path, proxy hint, config source reporting #4387 (setup wizard first-run path, 'ov serve' proxy hint, credentialPath misreport). The two PRs touch the same generated scripts/shared/* files; whichever lands second needs a node examples/memory-plugin-shared/sync.mjs re-run.
  • Whether Codex actually loads skills/ once the manifest declares it could not be verified non-interactively; the change follows the bundled Codex plugins' manifests.
  • Cursor and dsh (the other two harnesses with a skills directory) are not covered here; the shared core is harness-agnostic and only the install section needs porting.
  • The plugin READMEs and docs/en/agent-integrations still document server.url / claude_code / codex as ov.conf settings, which openviking-server rejects at startup (extra: forbid); the doctor warns about them, the docs are left for a follow-up.

Copilot AI lite review requested due to automatic review settings August 27, 2026 06:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

t0saki added 3 commits August 27, 2026 14:18
…cal deployments

When the resolved url is loopback the doctor now inspects the server side:
ov.conf startup blockers (plugin-only keys the server rejects, dev mode on a
non-loopback bind, empty root_api_key, port mismatch, relative workspace,
unexpanded $VAR secrets, provider credential rules), the server process and
port owner (pid file, lsof/ss, docker container and its /app/.openviking
mount), the vector index's recorded embedding vs the configured one, the
server log when log.output is a file, and GET /ready. Remote servers get the
/ready probe only. The docker pending_initialization stub is recognised in
the Connection section. Skills, references and READMEs describe the new
section; provider-level validation stays with openviking-server doctor.
…he port, plugin-only ov.conf keys and /ready

The section replicated the server's own config validation (top-level and
server.* key allowlists, provider credential rules, vlm, workers) and inspected
the pid file, docker mounts, systemd, the vector collection metadata and the
server log. All of that is what openviking-server reports itself at startup or
what `openviking-server doctor` covers, and the allowlists would drift with
every new config field. Keep what the server cannot tell the client: whether
anything listens on the port, the plugin-only ov.conf keys the server refuses
to start on, and GET /ready.

doctor-core.mjs is now synced only to the plugins that ship a doctor script;
the opencode and zcode copies were never imported.
@ZaynJarvis
ZaynJarvis merged commit 206054c into volcengine:main Aug 27, 2026
6 of 7 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in OpenViking project Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants