Skip to content

fix(sync): list autosync writes by subcommand - #220

Merged
marcus merged 1 commit into
marcus:mainfrom
jubishop:worktree-autosync-classification
Oct 17, 2026
Merged

marcus merged 1 commit into
marcus:mainfrom
jubishop:worktree-autosync-classification

Conversation

@jubishop

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #219, covering the cases its "Not in this PR" section listed except td dep <id> (see below), plus the reverse problem. The post-run autosync, a push and pull after a command, runs when the command matches the mutating-command list. Two kinds of mismatch remained.

Commands that write nothing that syncs, but still synced after they ran:

  • blocked-by and depends-on, which only read.
  • focus and unfocus, which only write session_state. That table doesn't sync.
  • The read-only subcommands of board, epic, task, and ws, which matched through their parent's name: board list, board show, epic list, task list, ws current, ws list, and ws show.

Commands that write but did not sync after they ran. By default, their changes waited for the next td command's startup sync:

  • defer and due, which update issues.
  • note's subcommands that write. Note events go to action_log and push when the sync_notes feature is on, which it is by default.

Changes:

  • Remove blocked-by, depends-on, focus, and unfocus from the list.
  • Replace board, epic, task, and ws with full paths for just their subcommands that write, using fix(sync): stop autosyncing after td comments listings #219's full-path matching.
  • Add defer, due, and note's add, archive, delete, edit, pin, restore, unarchive, and unpin subcommands.
  • Add TestSubcommandsAreClassified. It walks the subcommands of board, comments, epic, note, task, and ws through isMutatingInvocation. It fails when one is mutating but on the test's read-only list, or neither. A new subcommand of one of these parents fails the test until someone classifies it, which addresses the maintenance cost fix(sync): stop autosyncing after td comments listings #219 named.
  • TestMutationHookRunsOnlyForMutatingCommands now clears TD_SYNC_AUTO and TD_FEATURE_SYNC_AUTOSYNC. Either one set to false in the environment used to fail it under plain go test; make test already unsets TD_FEATURE_SYNC_AUTOSYNC.

The "configured for sync but autosync is off" warning follows the same classification, so the commands that stop syncing also stop printing it, and defer, due, and note writes now can. Startup sync is unchanged. Removing the longest keys made gofmt realign the remaining one-word entries, so the diff of cmd/autosync.go is easier to read with whitespace hidden.

Testing

  • make test, go vet ./..., and gofmt pass. golangci-lint v2.13.1 with GOOS=linux and Go 1.27.0, as in CI, reports 0 issues. go test -race -count=1 -run 'Mutat|Classified|AutoSync|Autosync' ./cmd/ passes.

  • Tests, all of which fail with main's list:

    • TestIsMutatingCommand and TestIsMutatingInvocation cover examples of the new reads and writes. The second resolves real invocations through rootCmd.
    • TestMutationHookRunsOnlyForMutatingCommands, renamed from fix(sync): stop autosyncing after td comments listings #219's TestMutationHookSkipsCommentsListing, now also runs the hook for board list, board create, blocked-by, due, and note add.
    • TestSubcommandsAreClassified, described above. It also fails if a parent key such as ws is added back.
  • Manual: real binaries in the e2e harness, with autosync on and on_start off, so each count is the post-run sync alone. Each read-only command ran with nothing pending; with changes pending, main also pushes them. Nothing was pending at the end of either run.

    Command main This branch
    board list, board show, epic list, task list, blocked-by, depends-on, focus, unfocus, ws current, ws list, ws show 1 pull none
    note add, defer, due none push and pull
    board create, ws start push and pull push and pull
    note list, note show none none

Not in this PR

  • Some commands still count as mutating in read-only modes chosen by arguments or flags rather than by path: td dep <id> with one argument (with or without --blocking), td undo --list, td import --dry-run, and td unstart --session or --stale without --force. The list can't express those. The command itself would have to tell the post-run hook that it wrote nothing. monitor stays mutating, since you can edit from inside it.
  • init also writes nothing that syncs. A new project has no sync state, so it never autosyncs. Re-running it in a linked project stops at ".todos/ already exists" and then autosyncs. I left it listed to keep this change to the cases above.
  • With sync_notes off, the pending-event count still includes note events, though push skips them and they stay pending. After this change, a note write prints Warning: sync: N local change(s) not yet pushed to remote (will retry on next td command). On main, the next command that autosynced printed the same warning, so the count was already wrong; this change only shows it sooner. I confirmed both in the e2e harness with TD_FEATURE_SYNC_NOTES=false. Excluding those events from the count seemed better as its own change.

🤖 Generated with Claude Code

blocked-by, depends-on, focus, unfocus, and the list, show, and current
subcommands of board, epic, task, and ws write nothing that syncs, so they no
longer push and pull after they run. defer, due, and note's subcommands that
write now sync after they run. A test fails when one of these parents gains a
subcommand that is not classified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@marcus
marcus merged commit 2a90134 into marcus:main Oct 17, 2026
3 checks passed
@jubishop
jubishop deleted the worktree-autosync-classification branch October 18, 2026 01:39
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.

2 participants