Skip to content

Commit 48f5618

Browse files
committed
harden harness doctor failure handling
1 parent 4f89ac9 commit 48f5618

7 files changed

Lines changed: 31 additions & 7 deletions

File tree

src/control-plane-operations.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ export async function applySkillMigration({ candidates, selectedSourcePaths, ove
266266
backupPath,
267267
installedFingerprint,
268268
});
269+
await writeManifest(tx.dir, tx.manifest);
269270
}
270271
await writeManifest(tx.dir, tx.manifest);
271272
} catch (error) {

src/privacy-metrics.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ export function metricsPath(home) {
4646
async function readState(home) {
4747
try {
4848
const parsed = JSON.parse(await readFile(metricsPath(home), "utf8"));
49-
return { ...defaultState(), ...parsed };
49+
const state = { ...defaultState(), ...parsed };
50+
if (!state.days || typeof state.days !== "object" || Array.isArray(state.days)) state.days = {};
51+
return state;
5052
} catch {
5153
return defaultState();
5254
}

src/server.mjs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ async function handleRequest(req, res) {
494494
}
495495
const repairs = await findExactDuplicateRepairs(cachedData.items, scopeId);
496496
const report = computeHygieneReport(cachedData, scopeId, { exactDuplicateRepairs: repairs });
497-
await recordPrivacyMetric(HOME, "doctor_open", activeAdapter.id, { inventoryCount: cachedData.items.length });
497+
await recordPrivacyMetric(HOME, "doctor_open", activeAdapter.id, { inventoryCount: cachedData.items.length }).catch(() => {});
498498
return json(res, { ok: true, harness: cachedData.harness, ...report });
499499
}
500500

@@ -513,7 +513,7 @@ async function handleRequest(req, res) {
513513
filePath => isPathAllowed(filePath, harnessId, cachedData, { knownOnly: true }),
514514
);
515515
await freshScan();
516-
await recordPrivacyMetric(HOME, "repair_apply", activeAdapter.id, { inventoryCount: cachedData.items.length });
516+
await recordPrivacyMetric(HOME, "repair_apply", activeAdapter.id, { inventoryCount: cachedData.items.length }).catch(() => {});
517517
return json(res, result);
518518
} catch (error) {
519519
return json(res, { ok: false, error: error.message }, requestErrorStatus(error, 400));
@@ -542,7 +542,7 @@ async function handleRequest(req, res) {
542542
targetRootDir: targetPaths.rootDir,
543543
targetScope,
544544
});
545-
await recordPrivacyMetric(HOME, "migration_preview", activeAdapter.id, { inventoryCount: cachedData.items.length });
545+
await recordPrivacyMetric(HOME, "migration_preview", activeAdapter.id, { inventoryCount: cachedData.items.length }).catch(() => {});
546546
return json(res, {
547547
ok: true,
548548
sourceHarness: cachedData.harness,
@@ -588,7 +588,7 @@ async function handleRequest(req, res) {
588588
validateTarget: async filePath => isPathWithin(filePath, targetRoot),
589589
});
590590
if (result.migrated) await refreshScanCache(targetHarnessId);
591-
await recordPrivacyMetric(HOME, "migration_apply", activeAdapter.id, { inventoryCount: cachedData.items.length });
591+
await recordPrivacyMetric(HOME, "migration_apply", activeAdapter.id, { inventoryCount: cachedData.items.length }).catch(() => {});
592592
return json(res, result);
593593
} catch (error) {
594594
return json(res, { ok: false, error: error.message }, requestErrorStatus(error, 400));
@@ -602,7 +602,7 @@ async function handleRequest(req, res) {
602602
const result = await undoControlPlaneTransaction(transactionId, CONTROL_DIR, isKnownControlPlanePath);
603603
invalidateCachedData(harnessId);
604604
for (const summary of await listAdapters()) invalidateCachedData(summary.id);
605-
await recordPrivacyMetric(HOME, kind === "migration" ? "migration_undo" : "repair_undo", activeAdapter.id);
605+
await recordPrivacyMetric(HOME, kind === "migration" ? "migration_undo" : "repair_undo", activeAdapter.id).catch(() => {});
606606
return json(res, result);
607607
} catch (error) {
608608
return json(res, { ok: false, error: error.message }, requestErrorStatus(error, 400));

src/ui/app.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,6 +989,10 @@ function setupResizer(resizerId, panelId, direction) {
989989

990990
function renderAll() {
991991
normalizeState();
992+
if (
993+
doctorReport &&
994+
(doctorReport.harness?.id !== selectedHarnessId || doctorReport.contextMap?.scope?.id !== selectedScopeId)
995+
) clearDoctorBadge();
992996
updateCapabilityVisibility();
993997
updateHarnessSelector();
994998
updateHarnessBranding();
@@ -4708,6 +4712,14 @@ function closeHarnessDoctor() {
47084712
document.getElementById("doctorModal")?.classList.add("hidden");
47094713
}
47104714

4715+
function clearDoctorBadge() {
4716+
const badge = document.getElementById("doctorBadge");
4717+
if (!badge) return;
4718+
badge.textContent = "";
4719+
badge.removeAttribute("title");
4720+
badge.classList.add("hidden");
4721+
}
4722+
47114723
async function refreshDoctorInventory() {
47124724
const previousScope = selectedScopeId;
47134725
data = await fetchJson(apiUrl("/api/scan"));
@@ -4811,6 +4823,7 @@ function renderDoctorPrivacy(status) {
48114823
async function loadHarnessDoctor() {
48124824
doctorReport = null;
48134825
doctorMigrationPreview = null;
4826+
clearDoctorBadge();
48144827
document.getElementById("doctorOverview").innerHTML = `<div class="doctor-loading">Auditing the selected scope…</div>`;
48154828
document.getElementById("doctorContextMap").innerHTML = "";
48164829
document.getElementById("doctorFindings").innerHTML = "";

src/ui/style.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1174,7 +1174,7 @@ body {
11741174
}
11751175
.doctor-hdr h3 { margin: 4px 0 4px; font-size: 1.35rem; }
11761176
.doctor-hdr .modal-sub { margin: 0; }
1177-
.doctor-body { max-height: calc(88vh - 105px); overflow: auto; padding: 18px 24px 26px; }
1177+
.doctor-body { max-height: calc(min(88vh, 920px) - 105px); overflow: auto; padding: 18px 24px 26px; }
11781178
.doctor-section { padding: 18px 0; border-bottom: 1px solid var(--border); }
11791179
.doctor-section:last-child { border-bottom: 0; }
11801180
.doctor-section-hdr { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; margin-bottom: 12px; }

tests/e2e/dashboard.spec.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3771,6 +3771,10 @@ test.describe('Harness Doctor control plane', () => {
37713771
await expect(page.locator('#doctorMigrationTarget option')).toHaveCount(2);
37723772
await expect(page.locator('#doctorMetricsEnabled')).not.toBeChecked();
37733773
expect(errors).toEqual([]);
3774+
3775+
await page.locator('#doctorClose').click();
3776+
await page.locator('.s-scope-hdr[data-scope-id="global"] .s-nm').click();
3777+
await expect(page.locator('#doctorBadge')).toHaveClass(/hidden/);
37743778
});
37753779

37763780
test('copy-only skill migration previews, applies, and undoes', async () => {

tests/unit/test-control-plane.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ describe("privacy metrics", () => {
263263
recordPrivacyMetric(home, "doctor_open", "claude")
264264
));
265265
assert.equal((await getPrivacyMetricsStatus(home)).sharePreview.events.doctor_open, 11);
266+
267+
await writeFile(metricsPath(home), JSON.stringify({ enabled: true, secret: "local-test", days: null }));
268+
assert.equal(await recordPrivacyMetric(home, "doctor_open", "claude"), true);
269+
assert.equal((await getPrivacyMetricsStatus(home)).sharePreview.events.doctor_open, 1);
266270
} finally {
267271
await rm(home, { recursive: true, force: true });
268272
}

0 commit comments

Comments
 (0)