Skip to content

[bug] --dry-run still writes: recall maintenance/promote run for real, writes past the loader, and flags that are ignored #900

Description

@SaulMoro

Description

--dry-run promises "no changes made". It still writes, in three ways:

B  the flag never reaches the command        recall maintenance / promote run for real
C  the command writes before its dry-run guard  pulls, clones, locks, state saves
D  the command ignores the flag               runs for real

Code refs are main at 417e704.

Status

Updated 2026-10-01 against main at b2d3598.

Item Owner State
B recall maintenance / recall promote #903 merged
D projects set #905 merged
D exclude add / remove #906 merged
D recall enable / disable #907 merged
D review --apply / --reject / --all-apply #930 merged
D mcp remove #937 merged
D webhook test #941 merged
C10 pull self-heals .teamai/.gitignore #944 open
D init --dry-run (single-repo path) #852 open, conflicting
Loaders that persist a migration on a dry or read-only run #901 (not this issue; see #893) merged
Everything below marked open up for grabs

Related, same files: #895 (merged) rewrites loadTeamValues, so C4 can build on it now. #899 and #901 (both merged) touch config.ts; #901 sits 2-3 lines above the pulls in C5, so C5 is unblocked.

Do first: a dry run can discard commits

pullRepo (src/utils/git.ts:312-352) falls back to git reset --hard origin/<b> when pull --ff-only fails. C1 and C5 call it before their guard.

Fix rule

allowed in a preview   git fetch (FETCH_HEAD, remote-tracking refs)
not allowed            pull / reset / checkout · clone into a cache · lock · config, state or manifest write
cheap preview          print what would change, return before the write
no cheap preview       refuse: exit 1, "teamai <cmd> has no --dry-run preview, nothing was run"

C. Writes before the guard (open)

# Command Writes on a dry run Fix
C1 remove pullRepo remove.ts:77, saveStateForScope :98; self mode force-removes and recreates knowledge-wt fetch only; keep the reconciled records in memory (publishedNameFor :136 reads them back); self mode reads the checkout's .teamai
C2 source add clones into ~/.teamai/sources/<name>/repo (source.ts:149), or pulls it stop before the clone: "Would clone ..." (the publicSkills warning needs the clone; say it was skipped)
C3 import --from-repo, --from-repo-list, --from-org deletes and re-clones the cache (clone.ts:162), or reset --hard it (:318-338); import lock; LLM scan; --from-org reads a stale whitelist file stop before clone, lock and LLM: ls-remote, compare with LAST_SYNC, "Would import ..."; --from-org keeps the list in memory
C4 models switch loadValuesFor models-cmd.ts:154 saves re-bound keys forward { dryRun } (:154, :370); rebase on #895
C5 roles init/add/remove/update, projects add/update/remove pullLatest (manifest-edit.ts:9) before each guard; self mode creates a worktree fetch, read the manifest at origin/<b> (getFileContentAtRev); self mode reads main; land after #901
C6 stats bare resolveConfigForDir per event dir (dashboard-scope.ts:50,103, session-owners.ts:82,357); seeds session-owners.jsonl; refreshes the reports checkout resolveConfigForDir(dir, options), scope helpers always { dryRun: true }; no seed on read
C7 recall maintenance (all modes), recall promote loadUserVotes rewrites every v1 votes file (votes.ts:199-202) read-only readUserVotes for these paths
C8 source browse clones or pulls (source.ts:343) with --dry-run: read the cache, or "Would clone ..."
C9 recall <query> --dry-run quality record (recall.ts:649-653, deliberate), index rebuild saved (:330-390) record nothing, build the index in memory
C10 pull (self mode) refreshTeamRepo runs migrateSelfModeGitignore, rewriting the tracked .teamai/.gitignore above every guard pass options, self-heal only on a real run (#944)

D. Ignores the flag

Command Where Fix Owner
projects set projects-cmd.ts:82-128 preview #905 (merged)
exclude add / remove exclude.ts:47-80 preview #906 (merged)
recall enable / disable recall-toggle.ts:125-141 preview #907 (merged)
init init.ts preview (single-repo) #852 (conflicting)
hooks inject hooks-cmd.ts:97-125 preview: reconcileTeamHooksForConfig takes dryRun open
mcp remove mcp-cmd.ts:116-124 preview: reconcileMcpForConfig(..., { removeAll, dryRun }) #937 (merged)
review --apply / --reject / --all-apply review-cmd.ts:152,169,232 preview #930 (merged)
update update.ts:672-686 behave as --check open
recall feedback votes.ts:435 preview open
models add / configure / remove index.ts actions skip program.opts() merge options, preview; rebase on #895 open
bind-project local-agent config preview open
webhook test sends HTTP preview: "Would send to N endpoint(s)" #941 (merged)
hooks remove, codebase --extract, init --http no preview path refuse open

Out of scope: hidden commands called only by hooks (hook-dispatch, track, source reconcile-plugins, ...); env (#880).

After everything lands

A guard in the preAction hook that refuses --dry-run on any command not on an allowlist, so a new command can't silently run for real:

key       full command path (leaf names collide: add x7, remove x10, init)
where     top of preAction, before the hook-subcommand early return (index.ts:91)
refuse    thisCommand.error(msg, { exitCode: 1 })

Reproduction

  1. teamai recall maintenance --prune --dry-run with a prunable learning: it is deleted (B).
  2. teamai hooks inject --dry-run: tool settings change (D).
  3. teamai roles add x --namespaces x --dry-run on a clone with diverged local commits: pullRepo resets it (C5).

Environment

  • teamai: main at 417e704
  • Provider: any
  • AI tool(s): any

Logs

How this was found

B was reproduced with the real CLI (see #903). The C and D entries come from reading the code at 417e704. They were not each run.

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions