Skip to content

feat: fail fast when two chatwoot processes mutate the same conversation - #30

Merged
scmmishra merged 8 commits into
mainfrom
feat/action-lock
Jul 31, 2026
Merged

feat: fail fast when two chatwoot processes mutate the same conversation#30
scmmishra merged 8 commits into
mainfrom
feat/action-lock

Conversation

@scmmishra

Copy link
Copy Markdown
Member

Running chatwoot conv 123 reply "hi" in two terminals at once sends the reply twice, since nothing coordinates concurrent invocations. This is easy to hit when scripts or agents drive the CLI, and the same race applies to every mutating verb.

This PR adds an exclusive per-conversation file lock (~/.chatwoot/locks/conv-<id>.lock) that all mutating conv verbs (reply, resolve, open, pending, snooze, assign, unassign, label, priority) acquire around their API call. The losing process fails fast with "another chatwoot command is already running on this conversation" instead of waiting, because a queued duplicate would still fire once the holder finished. The lock is an OS advisory lock (flock on unix, LockFileEx on Windows, promoting golang.org/x/sys to a direct dependency), so the kernel releases it if the process dies and no stale-lock cleanup is needed. Lock files are never unlinked on release, since deleting them races with other processes locking the same path.

Tested at both layers: unit tests for conflict/release semantics, plus a command-level test that runs every verb against an empty App while the lock is held, proving they bail out before reaching the API client.

Limitations: this only serializes processes on one machine, and it only guards the overlap window. Sequential duplicate replies are a separate problem.

Exclusive, non-blocking advisory locks in ~/.chatwoot/locks/, one file per
conversation ID. flock on unix, LockFileEx on Windows (promotes x/sys to a
direct dependency). The OS releases the lock if the process dies, so there
is no stale-lock cleanup.
conv reply, resolve, assign, and snooze now take the per-conversation lock
around their API call, so running e.g. reply concurrently from two terminals
sends once and errors once instead of double-sending. Fail-fast rather than
wait: a queued duplicate would still fire after the holder finishes.
…n lock

Completes lock coverage across all mutating conversation verbs, so any
overlapping pair of them fails fast instead of double-running.
Completes lock coverage: every mutating conv verb now takes the lock.
@scmmishra

Copy link
Copy Markdown
Member Author

@codex please review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9e08ea1f37

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/cmd/conversation.go
resolveAgent hits the agents/profile API when --agent is a name or an
uncached me, so locking after it meant a lock conflict could surface as a
masked lookup error instead of failing fast before any request. Resolution
now happens while the lock is held.
govulncheck flagged the Encrypted Client Hello privacy leak in the 1.26.4
standard library, reachable through the SDK's HTTP client.
@github-actions

Copy link
Copy Markdown

⚠️ Note: Baseline coverage from main branch is not available (artifact may be expired). Showing current coverage for changed files only.

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/chatwoot/cli/internal/cmd 48.50% (+48.50%) 🌟
github.com/chatwoot/cli/internal/lock 76.92% (+76.92%) 🌟

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/chatwoot/cli/internal/cmd/conversation.go 47.16% (+47.16%) 229 (+229) 108 (+108) 121 (+121) 🌟
github.com/chatwoot/cli/internal/lock/lock.go 71.43% (+71.43%) 21 (+21) 15 (+15) 6 (+6) 🌟
github.com/chatwoot/cli/internal/lock/lock_unix.go 100.00% (+100.00%) 5 (+5) 5 (+5) 0 🌟
github.com/chatwoot/cli/internal/lock/lock_windows.go 0.00% (ø) 0 0 0

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

Changed unit test files

  • github.com/chatwoot/cli/internal/cmd/conversation_lock_test.go
  • github.com/chatwoot/cli/internal/lock/lock_test.go

@scmmishra
scmmishra merged commit 5ea9598 into main Jul 31, 2026
10 checks passed
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