The skill run banner no longer corrupts the MCP transport. runSkillPackage wrote its banner with console.log, and it has two callers that disagree about what stdout is: on the CLI it is the operator's terminal, under knowl serve it is the JSON-RPC frame stream. knowl_skill_run calls the same function inside a stdio MCP server, so the banner was interleaved into the protocol and the client failed to parse the response to a call whose skill had actually run — an action taken, reported as a transport error. It goes to stderr now, the choice knowl serve already makes for its own startup banner.
Creating the global store no longer rebinds the process. ensureGlobalStore bootstrapped ~/.knowl/global.db through initDbPath, which assigns the module-level context every unscoped store operation resolves through. On the CLI that is invisible because the process exits; under knowl serve the process outlives the call, so one knowl_store with namespace: 'global' rebound the ambient database and every later project write in that session went to the wrong file while reporting success. Bootstrap runs through withDbPath instead.
A caller-chosen revision reaches git as an operand. listChangedFilesSince and listRenamedPathsSince interpolated a commit into an argv option position, and the caller is not always the engine — knowl_drift exposes since as an MCP tool argument accepting any string, so a prompt-injected agent chooses it. spawnSync runs without a shell, so this was never command injection; it was git-option injection, which is enough, because git diff --output=<path> exits 0 and writes the diff over the named file. The separator is --end-of-options and deliberately not --, which to git diff means "pathspecs follow" and would silently demote the range to a path.
--local excludes in the store that holds the atom, on both surfaces. The global branch of knowl store printed "Marked local. It will not be published." and recorded nothing, so the promise was made by the message alone. knowl_store had the same gap in a different shape: it ran the exclusion outside the withDbPath scope that wrote the atom, so for every non-project namespace the atom landed in one database and its cloud_excluded row in another. The namespace store's publisher reads its own exclusion table, finds nothing, and stages an atom the caller was told would never be published — and the global namespace is not exempt from that seam, since maybeAutoStage skips only session and the machine store can itself be cloud-connected.