akm-cli
Version:
akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur
57 lines (56 loc) • 3.01 kB
JavaScript
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
/**
* Shared index.db contention reclassification (field follow-up to #956).
*
* Extracted out of `indexer.ts` so both `akmIndex`'s outer catch AND
* `generateEmbeddingsForDb`'s own catch (`materialize-embeddings.ts`) can
* reuse the ONE classifier instead of each building a raw
* `Semantic search verification failed: <driver message>` string. Living in
* its own module (rather than one importing the other) avoids the import
* cycle `indexer.ts` <-> `materialize-embeddings.ts` would otherwise form.
*/
import { AkmError, TransientError } from "../core/errors.js";
import { probeLock } from "../core/file-lock.js";
import { formatLockHolderPid } from "../core/run-lock.js";
import { isSqliteContentionError } from "../core/state-db.js";
import { indexRebuildLockPath } from "./index-rebuild-lock.js";
/**
* Read-only description of the rebuild lock's current holder, appended to a
* reclassified index.db contention message when known (field follow-up to
* #956). `probeLock` only inspects the sentinel — it never acquires or
* mutates it — so this is safe to call from inside an error path.
*/
function describeIndexRebuildLockHolder() {
const probe = probeLock(indexRebuildLockPath());
if (probe.state !== "held")
return "";
return ` The rebuild lock is currently held by pid ${formatLockHolderPid({
pid: probe.holderPid,
launcherPid: probe.launcherPid ?? null,
})}.`;
}
/**
* Reclassify a contention-shaped error escaping the walk, index, or
* embedding phase into a retryable-shortly `TransientError` (field
* follow-up to #956, dev-team field review 2026-09-10): a concurrent writer
* (another `akm index`, a source-update embedding pass, the per-command
* background reindex) can make index.db busy, and the raw SQLite driver
* error ("database is locked") used to escape as exit 70
* (internal/unclassified) instead of the "retry shortly" contract exit 75
* gives a scheduler to branch on — mirroring `STATE_DB_CONTENDED`'s
* precedent for state.db (`core/state-db.ts`). Reuses the ONE shared
* classifier, `isSqliteContentionError`, rather than a second one. An error
* that is already a classified akm error (e.g. a `STATE_DB_CONTENDED`
* TransientError from an inner state.db write) is never re-wrapped — only a
* raw, unclassified error matching the shared contention shape is
* reclassified. Every other error is rethrown unchanged.
*/
export function reclassifyIndexDbContention(error) {
if (error instanceof AkmError || !isSqliteContentionError(error))
return error;
const contended = new TransientError(`akm's index database is busy (another akm process is writing it); retry shortly.${describeIndexRebuildLockHolder()}`, "INDEX_DB_CONTENDED");
contended.cause = error;
return contended;
}