Skip to content

Commit 211a9a0

Browse files
willkellyclaude
andauthored
fix(memory): add explicit iterator cleanup to prevent SQLite lock errors (#2265)
Add explicit iter.return() calls in finally blocks for selectFacts() and getLabels() to ensure prepared statements are properly reset after iteration. JavaScript for-of loops don't call iterator.return() on normal completion - only on break/throw/early-return. The @db/sqlite library requires .return() to properly reset prepared statements. Without it, statements can remain "active" causing "cannot commit transaction - SQL statements in progress" errors under concurrent load. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent af33de5 commit 211a9a0

File tree

1 file changed

+40
-28
lines changed

1 file changed

+40
-28
lines changed

packages/memory/space.ts

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -678,17 +678,23 @@ export const selectFacts = function <Space extends MemorySpace>(
678678
{ the, of, cause, is, since }: FactSelector,
679679
): SelectedFact[] {
680680
const stmt = getPreparedStatement(store, "export", EXPORT);
681-
const results = [];
682-
for (
683-
const row of stmt.iter({
684-
the: the === SelectAllString ? null : the,
685-
of: of === SelectAllString ? null : of,
686-
cause: cause === SelectAllString ? null : cause,
687-
is: is === undefined ? null : {},
688-
since: since ?? null,
689-
}) as Iterable<StateRow>
690-
) {
691-
results.push(toFact(row));
681+
const results: SelectedFact[] = [];
682+
const iter = stmt.iter({
683+
the: the === SelectAllString ? null : the,
684+
of: of === SelectAllString ? null : of,
685+
cause: cause === SelectAllString ? null : cause,
686+
is: is === undefined ? null : {},
687+
since: since ?? null,
688+
}) as IterableIterator<StateRow>;
689+
// Explicit cleanup via finally - for-of loops don't call return() on normal
690+
// completion, leaving the prepared statement active and causing "cannot
691+
// commit transaction - SQL statements in progress" errors.
692+
try {
693+
for (const row of iter) {
694+
results.push(toFact(row));
695+
}
696+
} finally {
697+
iter.return?.();
692698
}
693699
return results;
694700
};
@@ -1172,23 +1178,29 @@ export function getLabels<
11721178
"getLabelsBatch",
11731179
GET_LABELS_BATCH,
11741180
);
1175-
for (
1176-
const row of stmt.iter({
1177-
the: LABEL_TYPE,
1178-
ofs: JSON.stringify(ofs),
1179-
}) as Iterable<StateRow>
1180-
) {
1181-
const labelFact = toFact(row);
1182-
set<FactSelectionValue, OfTheCause<FactSelectionValue>>(
1183-
labels,
1184-
labelFact.of,
1185-
labelFact.the,
1186-
labelFact.cause,
1187-
{
1188-
since: labelFact.since,
1189-
...(labelFact.is ? { is: labelFact.is } : {}),
1190-
},
1191-
);
1181+
const iter = stmt.iter({
1182+
the: LABEL_TYPE,
1183+
ofs: JSON.stringify(ofs),
1184+
}) as IterableIterator<StateRow>;
1185+
// Explicit cleanup via finally - for-of loops don't call return() on normal
1186+
// completion, leaving the prepared statement active and causing "cannot
1187+
// commit transaction - SQL statements in progress" errors.
1188+
try {
1189+
for (const row of iter) {
1190+
const labelFact = toFact(row);
1191+
set<FactSelectionValue, OfTheCause<FactSelectionValue>>(
1192+
labels,
1193+
labelFact.of,
1194+
labelFact.the,
1195+
labelFact.cause,
1196+
{
1197+
since: labelFact.since,
1198+
...(labelFact.is ? { is: labelFact.is } : {}),
1199+
},
1200+
);
1201+
}
1202+
} finally {
1203+
iter.return?.();
11921204
}
11931205

11941206
return labels;

0 commit comments

Comments
 (0)