Skip to content

AskTask installs a process-global auto-declining IHumanConnector as a side effect and never removes it #372

Description

@sroussey

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/prdanalysis/grades/2026-09-14/sec-detailed.md §4.7.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions