You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Three fixes from investigating "model-specific prompt: no matching entry
found" breaking third-party model launches:
1. Test-isolation L1c/L1d (data loss, root cause): in NODE_TEST_CONTEXT an
explicit CCV_LOG_DIR / CLAUDE_CONFIG_DIR is now only honored when it
points inside the OS temp root; anything else forces the private guard
dir with a loud warning. A ccv-hosted shell exports CCV_LOG_DIR=<real
~/.claude/cc-viewer> to every child, so a direct `node --test <file>`
run there (the everyday loop when developing cc-viewer inside ccv)
inherited the real user data dir through the explicit-value fast path
the existing guards did not cover, and pty-manager.test.js's IM-worker
fixture cleanup rmSync'd the user's real global system_prompt/ --
confirmed live; this is what silently deleted the saved deepseek-v4-pro
entry and produced the misleading spawn warning. npm run test was never
affected (script pins CCV_LOG_DIR=tmp). The wiped entry was restored
from the current preset.
2. ${...} template variables are now actually rendered at spawn (new
server/lib/system-prompt-render.js wired into spawnClaude). The editor
stores placeholders literal by design, but the variable renderer had
zero callers on the live path, so injected prompts reached the model
with literal ${model.name}/${os.platform}/... -- the whole "Dynamic
Parameter Documentation" feature was inert and the third-party-model
presets shipped broken text. Files without placeholders pass through
untouched (variable collection is lazy, once per spawn); unknown
placeholders stay literal so shell syntax like ${HOME} survives;
${model.name} strips the [1m] suffix; cwd-dependent variables resolve
against the launched workspace via createSystemPromptVariables(
overrides, { cwd }); render failure falls back to the raw file.
3. The "no matching entry found" spawn warning no longer fires on
intentional skips: buildSystemPromptFileArgs tags suppressed:'env'
(CCV_DISABLE_AUTO_SYSTEM_PROMPT=1) and suppressed:'manual-flag' (a
matched entry suppressed by a user-passed --system-prompt[-file]).
Tests: new system-prompt-render suite; L1c/L1d accept/reject cases in
logdir-test-guard; suppressed-marker cases in system-prompt-files; three
existing cases that asserted the old explicit-env semantics updated to
simulate production (read-only echo paths).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
console.warn(`[findcc] L1d test-isolation barrier: CLAUDE_CONFIG_DIR="${raw}" is not under the OS temp dir — forcing a private guard config dir (tests may only target disposable temp dirs)`);
console.warn(`[findcc] L1c test-isolation barrier: CCV_LOG_DIR="${raw}" is not under the OS temp dir — forcing a private guard LOG_DIR (tests may only target disposable temp dirs; use CCV_LOG_DIR=tmp or a mkdtemp path)`);
Copy file name to clipboardExpand all lines: history.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,12 @@
2
2
3
3
## Unreleased
4
4
5
+
- fix(test-isolation, **data loss**): new **L1c/L1d barriers** in `findcc.js` — in a test context (`NODE_TEST_CONTEXT`), an **explicit** `CCV_LOG_DIR` / `CLAUDE_CONFIG_DIR` is now only honored when it points inside the OS temp root; anything else is forced to the private guard dir with a loud warning. Root cause of a real data-loss class the existing L1/L1b guards missed: a ccv-hosted shell exports `CCV_LOG_DIR=<real ~/.claude/cc-viewer>` into every child (the claude pty, its Bash tool, nested shells), so a direct `node --test test/<file>.js` run there — the everyday dev loop when developing cc-viewer inside ccv — inherited the real user data dir straight through the explicit-value fast path (the NODE_TEST_CONTEXT guard only covered the *unset* case), and `test/pty-manager.test.js`'s IM-worker fixture cleanup (`finally { rmSync(join(LOG_DIR,'system_prompt'), {recursive}) }`) then **deleted the user's real global model-prompt directory** (confirmed live: this is what silently ate the saved deepseek-v4-pro entry and produced the misleading "model-specific prompt … no matching entry found" spawn warning; `npm run test` was never affected because the script pins `CCV_LOG_DIR=tmp`). All repo tests already use tmp-based dirs, so nothing legitimate changes; production semantics (no `NODE_TEST_CONTEXT`) are untouched. New L1c/L1d accept/reject cases in `logdir-test-guard`.
6
+
7
+
- feat(expert/system-prompt): **`${...}` template variables are now actually rendered at spawn** — new `server/lib/system-prompt-render.js`, wired into `pty-manager.spawnClaude`. The Edit System Prompt editor stores presets/entries with placeholders literal (by design), but nothing in the live pipeline ever substituted them: the variable renderer (`renderPreset`/`createSystemPrompt`/`createSystemPromptVariables`) had zero callers on the spawn path, so every model entry and sentinel injected via `--system-prompt-file`/`--append-system-prompt-file` reached the model with literal `${model.name}`, `${os.platform}`, `${memory.index}` … — the entire "Dynamic Parameter Documentation" feature was inert, and the third-party-model presets (deepseek/GLM/Qwen/kimi, whose whole point is replacing the Anthropic-specific system prompt) shipped broken text. Rendering rules: files without placeholders pass through untouched (zero cost — variable collection shells out to git and is now lazy + once per spawn); unknown placeholders stay literal (`missingVariableMode: 'keep'`, so prompt text quoting shell syntax like `${HOME}` survives); `${model.name}` resolves from the spawn's resolved model id with the `[1m]` context-window suffix stripped; cwd-dependent variables (git/cwd/memory) resolve against the launched workspace via a new `createSystemPromptVariables(overrides, { cwd })` param; rendered copies live under `<tmpdir>/cc-viewer-rendered-prompts/<pid>/`; any render failure falls back to injecting the raw file (never breaks the spawn). Tests: new `system-prompt-render` suite (substitution, laziness, unknown-var preservation, fallback, `[1m]` strip, opts.cwd).
8
+
9
+
- fix(expert/system-prompt): the spawn diagnostic **"modelId resolved but no matching entry found"** no longer fires when injection was *intentionally skipped* — `buildSystemPromptFileArgs` now tags `suppressed: 'env'` (kill-switch `CCV_DISABLE_AUTO_SYSTEM_PROMPT=1`) and `suppressed: 'manual-flag'` (a matched model entry suppressed by a user-passed `--system-prompt[-file]`/`--append-system-prompt[-file]`), and `pty-manager` keeps quiet on both; only a genuine no-entry miss warns. Suppressed-marker cases added to `system-prompt-files`.
10
+
5
11
- fix(im, review round): hardening from a six-role review of the drawer Start button. **P1**: the `starting` boolean + platform-guarded `finally` reset leaked permanently when the user switched platforms mid-poll — a loading Start button then appeared on every platform and never stopped spinning until a page reload; `starting` is now a platform-scoped `startingPlatform` (button shows/loads only for the platform being started, functional-update reset clears only its own round). **P2 server**: `'start'` now rejects with 400 `missing <fields>` when required cred/secret fields are unset (new shared `missingCreds`, same gate the `/test` route uses — previously it would persist `enabled:true` for a credential-less platform whose worker no-ops forever, and reconcile would respawn that zombie on every server restart), and flipping `enabled` via `/process` now hits the same empty-allowlist server-side audit warning as the config route (extracted shared `warnIfEmptyAllowlist` — the headless bind-first-conversation warning could previously be bypassed). **P2 client**: the start-success criterion is tightened from `process.state==='ready'` to ready **and** bridge connected (`connection.connected`/`connectionState==='connected'`) in both the drawer and `ImPlatformSettings.start` — `ready` only means the worker's HTTP identity service is up, so stale creds produced a green "Connected" toast contradicting the "Running, connecting…" badge; the drawer's failure toast now appends the server `detail`; the outer POST catch reports via `reportSwallowed` instead of discarding the error; and the drawer mirrors the settings panel's `busyRef` (start-poll pauses the 5s background poll whose failure branch could flash the badge back to "Disconnected" mid-boot) + `mountedRef` (no setState after a real unmount) guards. Tests: idempotent start (already-enabled → prefs file not rewritten, mtime-pinned), creds gate (400, nothing persisted or spawned), and audit-warning cases added to `im-routes-gap`.
6
12
7
13
- feat(im): the IM conversation-record drawer (对话记录) gains an inline **Start** button next to the status badge — when the worker is confirmed dead (`process.state === 'dead'`, badge shows "Disconnected"), the user can relaunch the bridge without detouring through the settings modal. The button POSTs the existing loopback-only `POST /api/im/:platform/process {action:'start'}` and then polls `/status` until the worker is truly ready (`state === 'ready'`, same 15s criterion as `ImPlatformSettings.start`; success/failure surfaced as toasts, badge transitions live through Starting… → Connected, a platform-switch mid-poll is guarded by ref comparison so no cross-platform state bleed). Server-side, the `'start'` action now **persists `enabled: true`** first (read-merge-write via `loadConfig`+`saveConfig`, creds/allowlist untouched): a worker spawned while the stored config says disabled no-ops its bridge in `im-bridge-core` and would not survive a restart reconcile, so "start" must mean "enable + spawn" — `'stop'`/`'restart'` semantics unchanged (disabling stays on the config route). Remote (LAN) clients never see the button (their trimmed `/status` carries no `process` info, and `/process` is loopback-only anyway). Reuses existing i18n keys (`ui.im.start`/`ui.im.startFailed`/`ui.im.statusConnected`), no new entries. Tests: `im-routes-gap` gains persist-on-start (creds preserved) and stop-does-not-touch-enabled cases.
0 commit comments