Skip to content

fix: agent-facing fixes for MCP results, git workspaces, dry runs and CLI edges - #517

Merged
jahvon merged 12 commits into
mainfrom
fix/mochi-reported-issues
Nov 9, 2026
Merged

jahvon merged 12 commits into
mainfrom
fix/mochi-reported-issues

Conversation

@jahvon

@jahvon jahvon commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

Summary

Fixes a batch of problems agents and people hit running flow through Mochi. Most of them cost an agent a retry or led it to a wrong conclusion: a check that exited non-zero came back as a tool error, a remove with nothing on stdin "succeeded" without removing anything, a push to a git workspace silently didn't reach --sync, and a git workspace holding only flow files couldn't reach the repository it operates on.

Notable Changes

  • MCP results: execute, run_command, run_python and run_executable return a run that started and finished as a normal result (output, exitCode), whatever the exit code; only a run that never started is an error. Output has CRLF normalized and carriage-return redraws reduced to their last frame.
  • list_executables include_args: adds each executable's positional args, flags with type and default, required markers, and prompt params, so an agent needs no get_executable per task. A prompt param left unanswered over MCP names its question and says to pass it in params.
  • Checkout binding: flow workspace checkout NAME PATH (or workspace add --checkout) binds a git workspace to the local clone its executables operate on, stored in user config. A dir of @ or @/sub resolves there, and runs get FLOW_CHECKOUT_DIR. When the caller is inside the same repository or one of its worktrees, the caller's working tree wins.
  • exec --dry-run / MCP dry_run: prints the resolved args, params, dir and command without running anything, resolving secrets, prompting, or creating temp dirs, so it is safe for an untrusted workspace.
  • Sync pulls git workspaces: flow sync, the global --sync flag and MCP sync fast-forward git-sourced workspaces before rescanning; one that can't be pulled is named and skipped. --no-git rescans without the network (write_flowfile's post-write refresh uses it), --git is deprecated, and --force no longer needs it.
  • Env: an unset $VAR in an arg default expands to empty, as in a shell. A missing env file is skipped with a warning instead of failing the run or dropping the other files.
  • CLI: confirmations without a terminal read a line from stdin, and no answer is a usage error asking for --yes (it exited 0 having done nothing). browse --list --workspace lists all of that workspace's namespaces. A verb and ref passed as one quoted argument get the split form suggested. The private-executable error points at --workspace. cache help no longer calls persistent values temporary.

Breaking changes are recorded under v2.5.0 in docs/breaking-changes.md.

flowchart LR
  subgraph run["flow <verb> <ref>"]
    R[resolve executable] --> D{--dry-run?}
    D -- yes --> P[print plan: env, dir, cmd]
    D -- no --> E[build env + checkout] --> X[run]
  end
  C[(user config<br/>gitWorkspaces.checkout)] --> E
  W[caller cwd<br/>same git common dir?] --> E
Loading

jahvon added 10 commits November 9, 2026 10:34
An arg default like $REGION passed through literally when REGION was unset,
so tasks received the text '$REGION'. Unset now expands to an empty string,
as in a shell; $$ still escapes a literal dollar sign.

A missing envFiles entry made LoadEnvFromFiles return nothing, dropping every
file in the list, and a missing param envFile failed the run. Missing files
are now skipped with a warning; other read errors still surface.

Refs flowexec/mochi#397
- workspace remove, vault remove, secret remove and config reset share one
  confirmation helper. No answer at all (closed or empty stdin) is now a usage
  error telling the caller to pass --yes, instead of exiting 0 having done
  nothing. workspace remove validates the name before prompting.
- browse --list --workspace lists every namespace in that workspace unless
  --namespace narrows it; it showed only the root namespace with no hint.
- A verb and ref passed as one quoted argument gets the split form suggested.
- The private-executable error points at --workspace, which already works for
  named executables, instead of switching the global workspace. The flag's
  help no longer claims it is limited to --cmd and --spec.
- cache help says values set outside a run persist; it called them temporary.

Refs flowexec/mochi#400, flowexec/mochi#401
A run that started and finished came back as an EXECUTION_FAILED tool error
whenever it exited non-zero, with its output escaped into the message. For
check-style tasks the exit code is the answer, and callers that treat a tool
error as a broken source dropped it. execute, run_command, run_python and
run_executable now return {output, exitCode, started} as a normal result
whatever the exit code; only a run that never started is an error. Output has
CRLF normalized and carriage-return redraws reduced to their last frame, and
the tail cap no longer splits a UTF-8 character.

list_executables takes include_args, which adds each executable's inputs:
positional args in order, flags with type and default, required markers, and
prompt params. Without it, an agent needed a get_executable call per task.

A prompt param left unanswered over MCP now names its question and says to
pass it in execute's params, rather than listing bare env keys.

Refs flowexec/mochi#403, flowexec/mochi#405
--sync and MCP execute's sync only rescanned the executable cache, so after a
push to a git workspace's branch, runs kept using the cached clone with no sign
it was stale. --sync now fast-forwards git-sourced workspaces first (never
resetting local changes) and warns, naming each one, when a pull fails. MCP
sync_executables passes --git for the same reason.

Refs flowexec/mochi#402
A git workspace that holds only flow files for another repository had no way to
reach the user's clone of that repository: 'dir: ./' meant wherever the process
started, so tasks worked only when launched from inside the clone and failed
from the desktop app, MCP without dir, or any other directory.

'flow workspace checkout NAME PATH' (or 'workspace add --checkout') records the
clone under gitWorkspaces in user config, since the path differs per machine.
An executable opts in with a dir of '@' or '@/sub', and every run gets the
resolved path as FLOW_CHECKOUT_DIR. When the caller is inside the same
repository or one of its worktrees (same git common directory), the caller's
working tree wins, so worktrees keep working. A non-git workspace is its own
checkout, so the same flow files run in both shapes. An unbound '@/' dir fails
with how to bind one.

Refs flowexec/mochi#398
The confirm form reports 'false' both for a 'no' and for no input at all, so an
empty or closed stdin still aborted with exit 0. Without a terminal the answer
is now read as a line: 'yes' (or y/true/1) confirms, any other answer aborts,
and no answer is a usage error asking for --yes.
There was no way to check a flow file change - new flags, envKey mapping,
defaults - without running it, and running a local copy of a workspace needs it
trusted first. --dry-run resolves the executable as a run would and prints the
plan as YAML: args and params by env key, the resolved dir, the command or
steps, and any prompt params still missing. It never runs a command, resolves a
secret, prompts, or creates a temp directory, so it is safe for an untrusted
workspace. MCP execute takes dry_run and reports started: false.

Refs flowexec/mochi#404
@codecov

codecov Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

@jahvon

jahvon commented Nov 9, 2026 •

Copy link
Copy Markdown
Member Author

Status: Done. Mochi picks this up in flowexec/mochi#406 (pinned to this branch's commit; re-pin after merge).

Try it

git fetch origin && git switch fix/mochi-reported-issues
flow build binary
.bin/flow <verb> <ref> --dry-run -- <args>

Verification

  • Verified: flow validate passes: generate, lint, unit and e2e tests, schema validation.
  • Verified by hand with a dev binary and a sandboxed config: --dry-run prints args, params, <secret>/<prompt> placeholders, missingPrompts and the @ dir. An unset $VAR default expands to empty. A missing envFiles entry warns. An MCP-sourced run with an unanswered prompt names the question. workspace remove </dev/null exits 2 asking for --yes, echo no | aborts with 0, and echo yes | removes. The joined "verb ref" hint appears. browse --list -w lists non-root namespaces.
  • New specs: checkout resolution against a real repo and worktree, @/ dir expansion, MCP result shape for non-zero exits, carriage-return normalization, include_args, LoadEnvFromFiles skipping missing files, the no-answer confirmation, and the dry-run e2e.
  • Verified: git workspace e2e specs run plain sync and sync --force against a local bare repo, and a new spec checks sync --no-git leaves the clone untouched.
  • Not verified: sync against a real network remote, and the checkout binding from the Mochi desktop app.

Risks

  • Breaking changes are listed under v2.5.0 in docs/breaking-changes.md. MCP clients that keyed failures on isError need to read exitCode.
  • --sync now does network I/O for git workspaces. A failed pull is a warning and doesn't stop the run.

@jahvon
jahvon marked this pull request as ready for review November 9, 2026 16:18
--sync and MCP sync already pull, so 'flow sync' rescanning without pulling
was the one place 'sync' meant something else, and every surface built on it
(the Mochi desktop's two sync menu items among them) had to choose. sync now
fast-forwards git workspaces before rescanning, skipping and naming any it
can't pull. --no-git rescans without the network, which write_flowfile's
post-write refresh now uses. --git is deprecated, and --force no longer needs it.
@jahvon
jahvon merged commit caf9313 into main Nov 9, 2026
23 checks passed
@jahvon
jahvon deleted the fix/mochi-reported-issues branch November 9, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant