src/task/kb/AskTask.ts:142-143:
if (!globalServiceRegistry.has(HUMAN_CONNECTOR)) {
globalServiceRegistry.registerInstance(HUMAN_CONNECTOR, ONE_SHOT_CONNECTOR);
}
where ONE_SHOT_CONNECTOR (:22-29) answers every request with
{ action: "decline", done: true }.
The reasoning for needing one is sound and written down — AiChatWithKbTask asks the human for a
next message before the iteration cap is checked, and sec ask is one question — but the scope is
process-global and permanent. Nothing unregisters it, and nothing scopes it to this run.
AskTask is a registered task (src/config/registerTasks.ts:38), so it is reachable from
sec-base task run, the web console form and the MCP tool surface — the same three surfaces
532f61d's commit message names as the reason to validate task inputs. In any process where more
than one task runs, an AskTask that runs first leaves a connector that silently declines every
later approval or elicitation, with no prompt shown to anyone.
Blast radius, honestly
sec-base mcp serve is insulated. Per @workglow/cli, each tool call runs against a child
registry carrying an McpElicitationConnector bound to that call, and the child wins resolution —
so a later tool call still reaches its own connector. But the has() check reads the global
registry, which has none, so AskTask still writes there on every call.
sec-base workflow run / agent, and any future in-process composition, are not. One
AskTask in a graph is enough to auto-decline everything after it in the same process.
sec ask itself is one command per process, so the CLI path is unaffected today. That is what
makes this latent rather than live.
Fix
Pass the connector through the run rather than the registry. AiChatWithKbTask().run(input, runConfig)
takes a run config; a run-scoped service registry (or an explicit connector on the input, if the
task grows one upstream) confines it to the turn that needs it. Failing that, register/unregister
around the call in a try/finally, which at least bounds it to execute.
A narrower variant worth considering either way: maxIterations: 1 means the only human request
this can generate is "what is your next message", so the connector exists to answer one question
that is already answered by the input. If @workglow/ai grew a way to say "no next turn", sec
would not need a connector at all.
Found during the 2026-09-14 review. Snapshot: workglow-dev/prd → analysis/grades/2026-09-14/sec-detailed.md §4.7.
src/task/kb/AskTask.ts:142-143:where
ONE_SHOT_CONNECTOR(:22-29) answers every request with{ action: "decline", done: true }.The reasoning for needing one is sound and written down —
AiChatWithKbTaskasks the human for anext message before the iteration cap is checked, and
sec askis one question — but the scope isprocess-global and permanent. Nothing unregisters it, and nothing scopes it to this run.
AskTaskis a registered task (src/config/registerTasks.ts:38), so it is reachable fromsec-base task run, the web console form and the MCP tool surface — the same three surfaces532f61d's commit message names as the reason to validate task inputs. In any process where morethan one task runs, an
AskTaskthat runs first leaves a connector that silently declines everylater approval or elicitation, with no prompt shown to anyone.
Blast radius, honestly
sec-base mcp serveis insulated. Per@workglow/cli, each tool call runs against a childregistry carrying an
McpElicitationConnectorbound to that call, and the child wins resolution —so a later tool call still reaches its own connector. But the
has()check reads the globalregistry, which has none, so
AskTaskstill writes there on every call.sec-base workflow run/agent, and any future in-process composition, are not. OneAskTaskin a graph is enough to auto-decline everything after it in the same process.sec askitself is one command per process, so the CLI path is unaffected today. That is whatmakes this latent rather than live.
Fix
Pass the connector through the run rather than the registry.
AiChatWithKbTask().run(input, runConfig)takes a run config; a run-scoped service registry (or an explicit connector on the input, if the
task grows one upstream) confines it to the turn that needs it. Failing that, register/unregister
around the call in a
try/finally, which at least bounds it toexecute.A narrower variant worth considering either way:
maxIterations: 1means the only human requestthis can generate is "what is your next message", so the connector exists to answer one question
that is already answered by the input. If
@workglow/aigrew a way to say "no next turn", secwould not need a connector at all.
Found during the 2026-09-14 review. Snapshot:
workglow-dev/prd→analysis/grades/2026-09-14/sec-detailed.md§4.7.