Problem
readers.iter_key_value_rows (src/agentgrep/readers.py#L282) issues its key scan via connection.execute(key_query, tuple(parameters)). CPython's sqlite3.Cursor.execute() steps the underlying statement once eagerly as part of the call itself, before any row is fetched by the caller — so the first row of a result set can already be materializing, and the query already fully planned and partially executed, by the time execute() returns control to Python.
Any cancellation check placed after execute() returns — including a fetchmany()-batched poll — cannot interrupt this first step. On an index-backed range scan (the production shape both Cursor IDE and VS Code use) this is typically fast, but on a table scan without a usable index, or a sufficiently large single-page read over a slow filesystem (WSL's 9p bridge), this one call can itself block for a meaningful, unbounded span with no opportunity for any Python-level poll to fire.
sqlite3.Connection.set_progress_handler() (a callback invoked periodically during query execution, able to abort the query by returning non-zero) or sqlite3.Connection.interrupt() (callable from another thread to abort whatever query is currently executing on a connection) are the stdlib's purpose-built tools for this and were not evaluated as part of the fix that batched the rest of the scan.
Suggested direction
Register a set_progress_handler callback on the connection (or call interrupt() from the cancellation-signaling side) so a cancellation request can abort the first blocking execute() step directly, not just the batches after it.
Problem
readers.iter_key_value_rows(src/agentgrep/readers.py#L282) issues its key scan viaconnection.execute(key_query, tuple(parameters)). CPython'ssqlite3.Cursor.execute()steps the underlying statement once eagerly as part of the call itself, before any row is fetched by the caller — so the first row of a result set can already be materializing, and the query already fully planned and partially executed, by the timeexecute()returns control to Python.Any cancellation check placed after
execute()returns — including afetchmany()-batched poll — cannot interrupt this first step. On an index-backed range scan (the production shape both Cursor IDE and VS Code use) this is typically fast, but on a table scan without a usable index, or a sufficiently large single-page read over a slow filesystem (WSL's 9p bridge), this one call can itself block for a meaningful, unbounded span with no opportunity for any Python-level poll to fire.sqlite3.Connection.set_progress_handler()(a callback invoked periodically during query execution, able to abort the query by returning non-zero) orsqlite3.Connection.interrupt()(callable from another thread to abort whatever query is currently executing on a connection) are the stdlib's purpose-built tools for this and were not evaluated as part of the fix that batched the rest of the scan.Suggested direction
Register a
set_progress_handlercallback on the connection (or callinterrupt()from the cancellation-signaling side) so a cancellation request can abort the first blockingexecute()step directly, not just the batches after it.