c2bf64d29ddc47ffcd512ce920b18612fcbd2855
5805
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c2bf64d29d |
chore: Remove vue-multiselect package and styles from codebase (#13585)
|
||
|
|
a75e0f73ad |
feat(V5): Update settings pages UI (#13396)
# Pull Request Template ## Description This PR updates settings page UI ## Type of change - [x] New feature (non-breaking change which adds functionality) ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules |
||
|
|
ac88ebf1d7 |
chore: ignore .claude directory in gitignore (#13584)
# Pull Request Template adds .claude to gitignore Co-authored-by: Tanmay Deep Sharma <32020192+tds-1@users.noreply.github.com> |
||
|
|
32e05fe893 |
feat(cloud-billing): cancel subscriptions at period end on deletion mark (#13580)
## How to reproduce In Chatwoot Cloud, mark an account for deletion from account settings while the account has an active Stripe subscription. Before this change, deletion marking did not explicitly mark subscriptions to stop renewing at period end. ## What changed This PR adds `Enterprise::Billing::CancelCloudSubscriptionsService` and calls it from the delete action path in `Enterprise::Api::V1::AccountsController`. The service lists only active Stripe subscriptions for the customer and sets `cancel_at_period_end: true` when needed. The account deletion schedule remains unchanged (existing static 7-day behavior), and Stripe deleted-event fallback behavior remains unchanged. ## How this was tested Added and updated specs: - `spec/enterprise/services/enterprise/billing/cancel_cloud_subscriptions_service_spec.rb` - `spec/enterprise/controllers/enterprise/api/v1/accounts_controller_spec.rb` Executed: - `bundle exec rspec spec/enterprise/services/enterprise/billing/cancel_cloud_subscriptions_service_spec.rb` - `bundle exec rspec spec/enterprise/controllers/enterprise/api/v1/accounts_controller_spec.rb:363` |
||
|
|
9b57190bec |
chore(deps): bump rack from 3.2.3 to 3.2.5 (#13569)
Bumps [rack](https://github.com/rack/rack) from 3.2.3 to 3.2.5. <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/rack/rack/blob/main/CHANGELOG.md">rack's changelog</a>.</em></p> <blockquote> <h1>Changelog</h1> <p>All notable changes to this project will be documented in this file. For info on how to format all future additions to this file please reference <a href="https://keepachangelog.com/en/1.0.0/">Keep A Changelog</a>.</p> <h2>Unreleased</h2> <h3>Security</h3> <ul> <li><a href="https://github.com/advisories/GHSA-r657-rxjc-j557">CVE-2025-61780</a> Improper handling of headers in <code>Rack::Sendfile</code> may allow proxy bypass.</li> <li><a href="https://github.com/advisories/GHSA-6xw4-3v39-52mm">CVE-2025-61919</a> Unbounded read in <code>Rack::Request</code> form parsing can lead to memory exhaustion.</li> <li><a href="https://github.com/advisories/GHSA-whrj-4476-wvmp">CVE-2026-25500</a> XSS injection via malicious filename in <code>Rack::Directory</code>.</li> <li><a href="https://github.com/advisories/GHSA-mxw3-3hh2-x2mh">CVE-2026-22860</a> Directory traversal via root prefix bypass in <code>Rack::Directory</code>.</li> </ul> <h3>SPEC Changes</h3> <ul> <li>Define <code>rack.response_finished</code> callback arguments more strictly. (<a href="https://redirect.github.com/rack/rack/pull/2365">#2365</a>, <a href="https://github.com/skipkayhil"><code>@skipkayhil</code></a>)</li> </ul> <h3>Added</h3> <ul> <li>Add <code>Rack::Files#assign_headers</code> to allow overriding how the configured file headers are set. (<a href="https://redirect.github.com/rack/rack/pull/2377">#2377</a>, <a href="https://github.com/codergeek121"><code>@codergeek121</code></a>)</li> <li>Add support for <code>rack.response_finished</code> to <code>Rack::TempfileReaper</code>. (<a href="https://redirect.github.com/rack/rack/pull/2363">#2363</a>, <a href="https://github.com/skipkayhil"><code>@skipkayhil</code></a>)</li> <li>Add support for streaming bodies when using <code>Rack::Events</code>. (<a href="https://redirect.github.com/rack/rack/blob/main/redirect.github.com/rack/rack/pull/2375">#2375</a>, <a href="https://github.com/unflxw"><code>@unflxw</code></a>)</li> <li>Add <code>deflaters</code> option to <code>Rack::Deflater</code> to enable custom compression algorithms like zstd. (<a href="https://redirect.github.com/rack/rack/issues/2168">#2168</a>, <a href="https://github.com/alexanderadam"><code>@alexanderadam</code></a>)</li> <li>Add <code>Rack::Request#prefetch?</code> for identifying requests with <code>Sec-Purpose: prefetch</code> header set. (<a href="https://redirect.github.com/rack/rack/pull/2405">#2405</a>, <a href="https://github.com/glaszig"><code>@glaszig</code></a>)</li> <li>Add <code>rack.request.trusted_proxy</code> environment key to indicate whether the request is coming from a trusted proxy.</li> </ul> <h3>Changed</h3> <ul> <li>Raise before exceeding a part limit, not after. (<a href="https://redirect.github.com/rack/rack/pull/2362">#2362</a>, <a href="https://github.com/matthew-puku"><code>@matthew-puku</code></a>)</li> <li>Rack::Deflater now uses a fixed GZip mtime value. (<a href="https://redirect.github.com/rack/rack/pull/2372">#2372</a>, <a href="https://github.com/bensheldon"><code>@bensheldon</code></a>)</li> <li>Multipart parser drops support for RFC 2231 <code>filename*</code> parameter (prohibited by RFC 7578) and now properly handles UTF-8 encoded filenames via percent-encoding and direct UTF-8 bytes. (<a href="https://redirect.github.com/rack/rack/pull/2398">#2398</a>, <a href="https://github.com/wtn"><code>@wtn</code></a>)</li> <li>The query parser now raises <code>Rack::QueryParser::IncompatibleEncodingError</code> if we try to parse params that are not ASCII compatible. (<a href="https://redirect.github.com/rack/rack/pull/2416">#2416</a>, <a href="https://github.com/bquorning"><code>@bquorning</code></a>)</li> </ul> <h3>Fixed</h3> <ul> <li>Multipart parser: limit MIME header size check to the unread buffer region to avoid false <code>multipart mime part header too large</code> errors when previously read data accumulates in the scan buffer. (<a href="https://redirect.github.com/rack/rack/pull/2392">#2392</a>, <a href="https://github.com/alpaca-tc"><code>@alpaca-tc</code></a>, <a href="https://github.com/willnet"><code>@willnet</code></a>, <a href="https://github.com/krororo"><code>@krororo</code></a>)</li> <li>Fix <code>Rack::MockResponse#body</code> when the body is a Proc. (<a href="https://redirect.github.com/rack/rack/pull/2420">#2420</a>, <a href="https://redirect.github.com/rack/rack/pull/2423">#2423</a>, <a href="https://github.com/tavianator"><code>@tavianator</code></a>, [<a href="https://github.com/ioquatix"><code>@ioquatix</code></a>])</li> </ul> <h2>[3.2.4] - 2025-11-03</h2> <h3>Fixed</h3> <ul> <li>Multipart parser: limit MIME header size check to the unread buffer region to avoid false <code>multipart mime part header too large</code> errors when previously read data accumulates in the scan buffer. (<a href="https://redirect.github.com/rack/rack/pull/2392">#2392</a>, <a href="https://github.com/alpaca-tc"><code>@alpaca-tc</code></a>, <a href="https://github.com/willnet"><code>@willnet</code></a>, <a href="https://github.com/krororo"><code>@krororo</code></a>)</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/rack/rack/commit/bb5f3555bd12b9065112353e829298b3b5623ceb"><code>bb5f355</code></a> Bump patch version.</li> <li><a href="https://github.com/rack/rack/commit/f9bde3bc2dde2771185ac1a7b7602a4d9fa0a0d8"><code>f9bde3b</code></a> Prevent directory traversal via root prefix bypass.</li> <li><a href="https://github.com/rack/rack/commit/93a68f58aa82aa48f09b751501f19f5e760dd406"><code>93a68f5</code></a> XSS injection via malicious filename in <code>Rack::Directory</code>.</li> <li><a href="https://github.com/rack/rack/commit/3b8b0d22d68a7fb30fdea40f838d0f95a05c134d"><code>3b8b0d2</code></a> Fix MockResponse#body when the body is a Proc (<a href="https://redirect.github.com/rack/rack/issues/2420">#2420</a>)</li> <li><a href="https://github.com/rack/rack/commit/4c24539777db8833d78f881680cd245878cfba31"><code>4c24539</code></a> Bump patch version.</li> <li><a href="https://github.com/rack/rack/commit/3ba5e4f22f55abac21037bb137e56e5c8e36b673"><code>3ba5e4f</code></a> Allow Multipart head to span read boundary. (<a href="https://redirect.github.com/rack/rack/issues/2392">#2392</a>)</li> <li>See full diff in <a href="https://github.com/rack/rack/compare/v3.2.3...v3.2.5">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/chatwoot/chatwoot/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
3f8b6857cc | Bump version to 4.11.0 | ||
|
|
73cecdbc26 |
fix: refresh and persist RubyLLM model registry on initialization
RubyLLM uses a static bundled models.json as its model registry. New models released by providers (e.g., gpt-5.1, gpt-5.2) are not available until the gem is updated, causing ModelNotFoundError for self-hosted users who configure newer models. Refresh the model registry synchronously during Llm::Config.initialize! to fetch the latest model list from configured providers and models.dev, then persist with save_to_json. This runs once per process on the first LLM request, adding ~1-2s to that request. Falls back gracefully to the bundled registry on failure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
d11e058db2 |
fix: refresh and persist RubyLLM model registry on initialization
RubyLLM uses a static bundled models.json as its model registry. New models released by providers (e.g., gpt-5.1, gpt-5.2) are not available until the gem is updated, causing ModelNotFoundError for self-hosted users who configure newer models. Refresh the model registry asynchronously via a background thread during Llm::Config.initialize! to fetch the latest model list from configured providers and models.dev, then persist with save_to_json. The bundled registry is available immediately; the refresh updates it in the background without blocking the first LLM request. Use double-checked locking with a mutex to prevent concurrent threads from spawning duplicate refresh operations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
f4900f45d5 |
fix: refresh and persist RubyLLM model registry on initialization
RubyLLM uses a static bundled models.json file as its model registry. New models released by providers (e.g., gpt-5.1, gpt-5.2) are not available until the gem is updated. This causes ModelNotFoundError for self-hosted users who configure newer models. Call RubyLLM.models.refresh! during Llm::Config.initialize! to fetch the latest model list from configured providers and models.dev. Persist the result with save_to_json to tmp/ruby_llm_models.json so subsequent processes load from disk instead of making HTTP calls. The file has a 1-hour TTL to balance freshness with performance. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
dae4f3ee13 |
fix: move llm call of captain outside transaction (#13559)
# Pull Request Template ## Description Please include a summary of the change and issue(s) fixed. Also, mention relevant motivation, context, and any dependencies that this change requires. Fixes: The LLM call was wrapped in a transaction. This is an anti-pattern and caused idle-connections which PG eventually terminated with `PQconsumeInput() FATAL: terminating connection due to idle-in-transaction timeout` This resulted in activity messages being missing in some conversations on captain handoff, failures queueing up for retry and captain responding long after conversation was marked open/snoozed. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. locally and specs ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> |
||
|
|
e75e8a77f6 |
feat(shopify): Add mandatory compliance webhooks with HMAC verification (#13549)
Fixes https://linear.app/chatwoot/issue/CW-6494/add-shopify-mandatory-compliance-webhooks-for-app-store-listing Shopify requires all public apps to handle three GDPR compliance webhooks before they can be listed on the App Store. Their automated review checks for these endpoints and verifies that apps validate HMAC signatures on incoming requests. We were failing both checks. This PR adds a single webhook endpoint at `POST /webhooks/shopify` that receives all three compliance events. When Shopify sends a webhook, it signs the payload with our app's client secret and includes the signature in the `X-Shopify-Hmac-SHA256` header. Our controller reads the raw body, computes the expected HMAC-SHA256 digest, and rejects mismatched requests with a 401. Shopify identifies the event type through the `X-Shopify-Topic` header. For `customers/data_request` and `customers/redact`, we simply acknowledge with a 200—Chatwoot doesn't persist any Shopify customer data. All order lookups happen as live API calls at query time. For `shop/redact`, which Shopify sends after a merchant uninstalls the app, we delete the integration hook for that shop domain and remove the stored access token and configuration. ### How to test via Rails console ``` secret = GlobalConfigService.load('SHOPIFY_CLIENT_SECRET', nil) body = '{"shop_domain":"test.myshopify.com"}' valid_hmac = Base64.strict_encode64(OpenSSL::HMAC.digest('SHA256', secret, body)) ``` #### Test 1: No HMAC → 401 ``` app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Topic' => 'customers/data_request' } app.response.code # => "401" ``` #### Test 2: Invalid HMAC → 401 ``` app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => 'invalid', 'X-Shopify-Topic' => 'customers/data_request' } app.response.code # => "401" ``` #### Test 3: Valid HMAC, customers/data_request → 200 ``` app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => valid_hmac, 'X-Shopify-Topic' => 'customers/data_request' } app.response.code # => "200" ``` #### Test 4: Valid HMAC, customers/redact → 200 ``` app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => valid_hmac, 'X-Shopify-Topic' => 'customers/redact' } app.response.code # => "200" ``` #### Test 5: Valid HMAC, shop/redact → 200 (deletes hook) ``` # First check if a hook exists for this domain: Integrations::Hook.where(app_id: 'shopify', reference_id: 'test.myshopify.com').count app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => valid_hmac, 'X-Shopify-Topic' => 'shop/redact' } app.response.code # => "200" ``` --------- Co-authored-by: Shivam Mishra <scm.mymail@gmail.com> |
||
|
|
229f56d6e3 |
chore: Remove vue-multiselect and migrate to next components (#13506)
# Pull Request Template ## Description This PR includes: 1. Removes multiselect usage from the Merge Contact modal (Conversation sidebar) and replaces it with the existing component used on the Contact Details page. 2. Replaces legacy form and multiselect elements in Add and Edit automations flows with next components.**(Also check Macros)** 3. Replace multiselect with ComboBox in contact form country field. 4. Replace multiselect with TagInput in create/edit attribute form. 5. Replace multiselect with TagInput for agent selection in inbox creation. 6. Replace multiselect with ComboBox in Facebook channel page selection ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? **Screenshots** 1. **Merge modal** <img width="741" height="449" alt="image" src="https://github.com/user-attachments/assets/a05a96ec-0692-4d94-9e27-d3e85fd143e4" /> <img width="741" height="449" alt="image" src="https://github.com/user-attachments/assets/fc1dc977-689d-4440-869d-2124e4ca9083" /> 2. **Automations** <img width="849" height="1089" alt="image" src="https://github.com/user-attachments/assets/b0155f06-ab21-4f90-a2c8-5bfbd97b08f7" /> <img width="813" height="879" alt="image" src="https://github.com/user-attachments/assets/0921ac4a-88f5-49ac-a776-cc02941b479c" /> <img width="849" height="826" alt="image" src="https://github.com/user-attachments/assets/44358dae-a076-4e10-b7ba-a4e40ccd817f" /> 3. **Country field** <img width="462" height="483" alt="image" src="https://github.com/user-attachments/assets/d5db9aa1-b859-4327-9960-957d7091678f" /> 4. **Add/Edit attribute form** <img width="619" height="646" alt="image" src="https://github.com/user-attachments/assets/6ab2ea94-73e5-40b8-ac29-399c0543fa7b" /> <img width="619" height="646" alt="image" src="https://github.com/user-attachments/assets/b4c5bb0e-baa0-4ef7-a6a2-adb0f0203243" /> <img width="635" height="731" alt="image" src="https://github.com/user-attachments/assets/74890c80-b213-4567-bf5f-4789dda39d2d" /> 5. **Agent selection in inbox creation** <img width="635" height="534" alt="image" src="https://github.com/user-attachments/assets/0003bad1-1a75-4f20-b014-587e1c19a620" /> <img width="809" height="602" alt="image" src="https://github.com/user-attachments/assets/5e7ab635-7340-420a-a191-e6cd49c02704" /> 7. **Facebook channel page selection** <img width="597" height="444" alt="image" src="https://github.com/user-attachments/assets/f7ec8d84-0a7d-4bc6-92a1-a1365178e319" /> <img width="597" height="444" alt="image" src="https://github.com/user-attachments/assets/d0596c4d-94c1-4544-8b50-e7103ff207a6" /> <img width="597" height="444" alt="image" src="https://github.com/user-attachments/assets/be097921-011b-4dbe-b5f1-5d1306e25349" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Shivam Mishra <scm.mymail@gmail.com> |
||
|
|
138840a23f |
fix: typo in metadata key in captain v2 (#13558)
# Pull Request Template ## Description ## Type of change typo fix ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules |
||
|
|
c5f6844877 | fix: Disable reply editor outside WhatsApp reply window (#13454) | ||
|
|
39243b9e71 |
fix: duplicate message_created webhooks for WhatsApp messages (#13523)
Some customers using WhatsApp inboxes with account-level webhooks were
reporting receiving duplicate `message_created` webhook deliveries for
every incoming message. Upon inspection, here's what we found
- Both payloads are identical.
- No errors appear in the application logs
- Webhook URL is only configured in one place.
This meant, the system was sending the webhooks twice. For some context,
there's a know related issue... Meta's WhatsApp Business API can deliver
the same webhook notification multiple times for a single message. The
codebase already acknowledges this — there's a comment in
`IncomingMessageBaseService#process_messages` noting that "multiple
webhook events can be received against the same message due to
misconfigurations in the Meta business manager account." A deduplication
guard exists, but it doesn't actually work under concurrency.
### Rationale
The existing dedup was a three-step sequence: check Redis (`GET`), check
the database, then set a Redis flag (`SETEX`). Two Sidekiq workers
processing duplicate Meta webhooks simultaneously would both complete
the `GET` before either executed the `SETEX`, so both would proceed to
create a message. The `source_id` column has a non-unique index, so the
database wouldn't catch the duplicate either. Each message then
independently fires `after_create_commit`, dispatching two
`message_created` webhook events to the customer.
```
Worker A Worker B
│ │
▼ ▼
Redis GET key ──► nil Redis GET key ──► nil
│ │
│ ◄── both pass guard ──► │
│ │
▼ ▼
Redis SETEX key Redis SETEX key
│ │
▼ ▼
BEGIN transaction BEGIN transaction
INSERT message INSERT message
DELETE Redis key ◄─┐ │
COMMIT │ DELETE Redis key
│ COMMIT
│ │
└── key gone before ───┘
B's commit lands
▼ ▼
after_create_commit after_create_commit
dispatch MESSAGE_CREATED dispatch MESSAGE_CREATED
│ │
▼ ▼
WebhookJob ──► n8n WebhookJob ──► n8n
(duplicate!)
```
There was a second, subtler problem visible in the diagram: the Redis
key was cleared *inside* the database transaction, before the
transaction committed. This opened a window where neither the Redis
check nor the database check would see the in-flight message.
The fix collapses the check-and-set into a single `SET NX EX` call,
which is atomic in Redis. The key is no longer eagerly cleared — it
expires naturally after 24 hours. The database lookup
(`find_message_by_source_id`) remains as a fallback for messages that
were created before the lock expired.
```
Worker A Worker B
│ │
▼ ▼
Redis SET NX ──► OK Redis SET NX ──► nil
│ │
▼ ▼
proceeds to create returns early
message normally (lock already held)
```
### Implementation Notes
The lock logic is extracted into `Whatsapp::MessageDedupLock`, a small
class that wraps a single `Redis SET NX EX` call. This makes the
concurrency guarantee testable in isolation — the spec uses a
`CyclicBarrier` to race two threads against the same key and asserts
exactly one wins, without needing database writes,
`use_transactional_tests = false`, or monkey-patching.
Because the Redis lock now persists (instead of being cleared
mid-transaction), existing WhatsApp specs needed an `after` hook to
clean up `MESSAGE_SOURCE_KEY::*` keys between examples. Transactional
fixtures only roll back the database, not Redis.
|
||
|
|
fb2f5e1d42 | fix: Persist compose form state on accidental outside click (#13529) | ||
|
|
cfe3061b5d |
feat: Allow removing labels via conversation context menu (#13525)
# Pull Request Template ## Description This PR adds support for removing labels from the conversation card context menu. Assigned labels now show a checkmark, and clicking an already-selected label will remove it. Fixes https://linear.app/chatwoot/issue/CW-6400/allow-removing-labels-directly-from-the-right-click-menu https://github.com/chatwoot/chatwoot/issues/13367 ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? **Screencast** https://github.com/user-attachments/assets/4e3a6080-a67d-4851-9d10-d8dbf3ceeb04 ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules |
||
|
|
aa7e3c2d38 |
feat: langfuse logging improvements (#13534)
Langfuse logging improvements ## Description Please include a summary of the change and issue(s) fixed. Also, mention relevant motivation, context, and any dependencies that this change requires. Fixes # (issue) For reply suggestion: the errors are being stored inside output field, but observations should be marked as errors. For assistant: add credit_used metadata to filter handoffs from ai-replies For langfuse tool call: add `observation_type=tool` ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. before: <img width="1028" height="57" alt="image" src="https://github.com/user-attachments/assets/70f6a36e-6c33-444c-a083-723c7c9e823a" /> after: <img width="872" height="69" alt="image" src="https://github.com/user-attachments/assets/1b6b6f5f-5384-4e9c-92ba-f56748fec6dd" /> `credit_used` to filter handoffs from AI replies that cause credit usage <img width="1082" height="672" alt="image" src="https://github.com/user-attachments/assets/90914227-553a-4c03-bc43-56b2018ac7c1" /> set `observation_type` to `tool` <img width="726" height="1452" alt="image" src="https://github.com/user-attachments/assets/e639cc9b-1c6c-4427-887e-23e5523bf64f" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules |
||
|
|
3874383698 |
feat: insrument captain v2 (#13439)
# Pull Request Template ## Description Instruments captain v2 ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. Local testing: <img width="864" height="510" alt="image" src="https://github.com/user-attachments/assets/855ebce5-e8b8-4d22-b0bb-0d413769a6ab" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Shivam Mishra <scm.mymail@gmail.com> |
||
|
|
101eca3003 |
feat: add captain editor events (#13524)
## Description
Adds missing analytics instrumentation for the editor AI funnel so we
can measure end-to-end usage and outcome quality.
### What was added
- Captain: Editor AI menu opened
- Captain: Generation failed
- Captain: AI-assisted message sent
### Behavior covered
- Tracks AI button click + menu open from both entry points:
- top panel sparkle button
- inline editor copilot button
- Tracks generation failures (initial + follow-up stages).
- Tracks whether accepted AI content was sent as-is or edited before
send.
### Notes
- Applies to editor Captain accept/send flow
(rewrite/summarize/reply_suggestion + follow-ups).
- Does not change Copilot sidebar flow instrumentation.
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update
## How Has This Been Tested?
### Manual verification steps
<img width="1906" height="832" alt="image"
src="https://github.com/user-attachments/assets/f0ade43b-aa8d-41be-8ca2-20a091a81f60"
/>
<img width="828" height="280" alt="image"
src="https://github.com/user-attachments/assets/be76219e-fb61-4a6e-bff5-dc085b0a3cc9"
/>
<img width="415" height="147" alt="image"
src="https://github.com/user-attachments/assets/36802c5c-33a7-49ed-bf7e-f0b02d86dccc"
/>
<img width="2040" height="516" alt="image"
src="https://github.com/user-attachments/assets/74b95288-bc86-4312-a282-14211ae8f25c"
/>
1. Open a conversation with Captain tasks enabled.
2. Click AI button in top panel and inline editor.
3. Confirm analytics events fire for:
- AI menu opened
4. Run an AI action and force a failure scenario (or empty response
path) and confirm generation-failed event.
5. Accept AI output, then:
- send without changes -> editedBeforeSend: false
- edit then send -> editedBeforeSend: true
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
|
||
|
|
61eaa098ae |
fix(messages): reduce audio transcription 400 retry noise (#13487)
## Summary This PR reduces duplicate failure noise for audio transcription jobs that fail with permanent HTTP 400 responses, and fixes a file-format edge case causing intermittent 400s. Sentry issue: [CHATWOOT-99E / 6660541334](https://chatwoot-p3.sentry.io/issues/6660541334/) ## Confirmed root cause For some attachments, the stored filename had no extension (example: `speech`, content type `audio/mpeg`). When the temporary transcription upload file was created without an extension, OpenAI returned: `Unrecognized file format` (HTTP 400). ## Scope of changes 1. `Messages::AudioTranscriptionJob` - Keeps `discard_on Faraday::BadRequestError` to avoid retry storms on permanent request errors. - Adds explicit Rails warning logs for discarded jobs with attachment/job/status context. 2. `Messages::AudioTranscriptionService` - Keeps guaranteed temp file cleanup via `ensure`. - Ensures temp upload files include an extension when the original filename has none, derived from blob `content_type`. - This addresses intermittent failures like extensionless `audio/mpeg` files. ## Reproduction Enable audio transcription for an account and process an audio attachment whose stored filename has no extension (for example `speech`) but valid audio content type (`audio/mpeg`). Before this fix, OpenAI transcription could return HTTP 400 `Unrecognized file format` for that attachment while similar attachments with extensions succeeded. ## Testing Ran: `bundle exec rubocop enterprise/app/jobs/messages/audio_transcription_job.rb enterprise/app/services/messages/audio_transcription_service.rb` Result: both modified files pass lint with no offenses. |
||
|
|
9cd7c4ef89 |
fix: Enhance notification emails with message details and handle failed messages (#13273)
## Description Handle messages with null content properly in UI and email notifications ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## Relevant Screenshots: <img width="688" height="765" alt="Screenshot 2026-01-21 at 4 43 00 PM" src="https://github.com/user-attachments/assets/6a27c22e-2ae6-4377-a05d-cfa44bf181fe" /> ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches notification email templates and message rendering conditions; mistakes could lead to missing content/attachments in emails or incorrect UI visibility, but changes are localized and non-auth/security related. > > **Overview** > Agent notification emails for *assigned* and *participating* new messages now include the actual message details (sender name, rendered text when present, and attachment links) and gracefully fall back when content is unavailable. > > To support this, the mailer now passes `@message` into Liquid via `MessageDrop` (adding `attachments` URLs), and the dashboard message UI now renders failed/external-error messages even when `content` is `null` while tightening retry eligibility to require content or attachments (and still within 1 day). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 475c8cedda54eb5e806990f977faf8098d0b27d8. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> |
||
|
|
f4538ae2c5 |
fix: Enforce team boundaries to prevent cross-team assignments (#13353)
## Description Fixes a critical bug where conversations assigned to a team could be auto-assigned to agents outside that team when all team members were at capacity. ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes core assignment selection for both legacy and v2 flows; misconfiguration of `allow_auto_assign` or team membership could cause conversations to remain unassigned. > > **Overview** > Prevents auto-assignment from crossing team boundaries by filtering eligible agents to the conversation’s `team` members (and requiring `team.allow_auto_assign`) in both the legacy `AutoAssignmentHandler` path and the v2 `AutoAssignment::AssignmentService` (including the Enterprise override). > > Adds test coverage to ensure team-scoped conversations only assign to team members, and are skipped when team auto-assign is disabled or no team members are available; also updates the conversations controller spec setup to include team membership. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 67ed2bda0cd8ffd56c7e0253b86369dead2e6155. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> |
||
|
|
fd5ac2a8a3 |
fix: apply installation branding replacement in tooltip copy (#13538)
## Summary
Fix hardcoded `Chatwoot` branding in two UI tooltips using the existing
`useBranding` flow so self-hosted/white-label deployments no longer show
the wrong brand text.
## Changes
- LabelSuggestion tooltip now uses:
- `replaceInstallationName($t('LABEL_MGMT.SUGGESTIONS.POWERED_BY'))`
- Message avatar tooltip (native app/external echo) now uses:
- `replaceInstallationName(t('CONVERSATION.NATIVE_APP_ADVISORY'))`
## Why
This follows the existing branding pattern already used in the product
and keeps behavior consistent across deployments.
## Notes
- No change to message logic or API behavior.
- `AGENTS.md` updated with a branding guidance note.
## Fixes
- Fixes https://github.com/chatwoot/chatwoot/issues/13306
- Fixes https://github.com/chatwoot/chatwoot/issues/13466
## Testing
<img width="195" height="155" alt="Screenshot 2026-02-13 at 3 55 39 PM"
src="https://github.com/user-attachments/assets/5b295cdd-6e5d-42c0-bbd7-23ba7052e1c3"
/>
<img width="721" height="152" alt="Screenshot 2026-02-13 at 3 55 48 PM"
src="https://github.com/user-attachments/assets/19cec2a0-451f-4fb3-bd61-7c2e591fc3c7"
/>
|
||
|
|
6b7180d051 |
fix(twilio): prevent dead jobs on missing channel lookup (#13522)
## Why We observed `Webhooks::TwilioEventsJob` failures ending up in Sidekiq dead jobs when Twilio callback payloads could not be mapped to a `Channel::TwilioSms` record. In this scenario, channel lookup raised `ActiveRecord::RecordNotFound`, which caused retries and eventual dead jobs instead of a graceful drop. Related Sentry issue/search: - https://chatwoot-p3.sentry.io/issues/?project=6382945&query=Webhooks%3A%3ATwilioEventsJob%20ActiveRecord%3A%3ARecordNotFound ## What changed This PR keeps the existing lookup flow but makes it non-raising: - `app/services/twilio/incoming_message_service.rb` - `find_by!` -> `find_by` for account SID + phone lookup - Added warning log when channel lookup misses - `app/services/twilio/delivery_status_service.rb` - `find_by!` -> `find_by` for account SID + phone lookup - Added warning log when channel lookup misses ## Reproduction Configure a Twilio webhook callback that reaches Chatwoot but does not match an existing Twilio channel lookup path. Before this change, the job raises `RecordNotFound` and can end up in dead jobs after retries. After this change, the job logs the miss and exits safely. ## Testing - `bundle exec rspec spec/services/twilio/incoming_message_service_spec.rb spec/services/twilio/delivery_status_service_spec.rb` - `bundle exec rubocop app/services/twilio/incoming_message_service.rb app/services/twilio/delivery_status_service.rb` |
||
|
|
4d362da9f0 |
fix: Prevent user enumeration on password reset endpoint (#13528)
## Description The current password reset endpoint returns different HTTP status codes and messages depending on whether the email exists in the system (200 for existing emails, 404 for non-existing ones). This allows attackers to enumerate valid email addresses via the password reset form. ## Changes ### `app/controllers/devise_overrides/passwords_controller.rb` - Removed the `if/else` branch that returned different responses based on email existence - Now always returns a generic `200 OK` response with the same message regardless of whether the email exists - Uses safe navigation operator (`&.`) to send reset instructions only if the user exists ### `config/locales/en.yml` - Consolidated `reset_password_success` and `reset_password_failure` into a single generic `reset_password` key - New message does not reveal whether the email exists in the system ## Security Impact - **Before**: An attacker could determine if an email was registered by observing the HTTP status code (200 vs 404) and response message - **After**: All requests receive the same 200 response with a generic message, preventing user enumeration This follows [OWASP guidelines for authentication error messages](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#authentication-responses). Fixes #13527 |
||
|
|
2c2f0547f7 |
fix: Captain not responding to campaign conversations (#13489)
Co-authored-by: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com> |
||
|
|
c7193c7917 |
fix(slack): handle archived channel errors in SendOnSlackJob (#13520)
When a Slack-integrated channel is archived, posting from Chatwoot raises `Slack::Web::Api::Errors::IsArchived` in `SendOnSlackJob`, which retries and can end up in dead jobs. This can be reproduced by archiving the connected Slack channel for a valid hook and creating outgoing messages. This change adds `IsArchived` to the existing handled Slack API rescue path in `Integrations::Slack::SendOnSlackService#send_message`, so archived-channel failures now follow the same flow as related Slack failures (`prompt_reauthorization!` + `disable`) instead of bubbling and retrying repeatedly. I tested this by running `bundle exec rubocop lib/integrations/slack/send_on_slack_service.rb` (with `rbenv` initialized), and it passes with no offenses. Sentry issue: https://chatwoot-p3.sentry.io/issues/7150427066/ |
||
|
|
d272a64ff7 |
fix(mailbox): handle malformed sender address headers (#13486)
## How to reproduce When an inbound email has malformed sender headers (for example `From: McDonald <info@example.com` without a closing `>`), mailbox processing can raise `Mail::Field::IncompleteParseError` while resolving sender data in `MailPresenter`. ## What changed This PR hardens sender parsing in `MailPresenter` with a small, readable implementation: - Added/used a safe parser (`parse_mail_address`) that rescues `Mail::Field::ParseError` and `Mail::Field::IncompleteParseError`. - `sender_name` now uses the same safe parser path. - `original_sender` now resolves candidates in order via a compact `filter_map` flow: - `Reply-To` - `X-Original-Sender` - `From` - All three candidates are parsed as email addresses before use (including `X-Original-Sender`), and invalid values are ignored. - `notification_email_from_chatwoot?` now compares sender addresses case-insensitively (`casecmp?`) to avoid case-only mismatches. ## Test coverage Added focused presenter specs for: - malformed `From` header returns nil sender values and does not classify as notification sender - malformed `Reply-To` falls back to valid `From` - valid `X-Original-Sender` is used when present - invalid `X-Original-Sender` falls back to valid `From` - mixed-case sender address still matches configured `MAILER_SENDER_EMAIL` ## How this was tested Ran: - `bundle exec rspec spec/presenters/mail_presenter_spec.rb` - `bundle exec rubocop app/presenters/mail_presenter.rb spec/presenters/mail_presenter_spec.rb` Sentry issue: [CHATWOOT-B9Y](https://chatwoot-p3.sentry.io/issues/7005483640/) |
||
|
|
b2cb3717e5 |
fix: Replace default Rails error pages with custom designs (#13514)
## Summary - Replace generic Rails 404, 422, and 500 error pages with custom-designed pages - Each page features a prominent gradient error code, clean typography, and a "Back to home" CTA - Full dark mode support via `prefers-color-scheme` media query - Mobile responsive design - No external dependencies (self-contained static HTML, works even when Rails is down) - No logo/branding to ensure compatibility with custom-branded self-hosted instances --- ## Before (default Rails 404) <img width="2400" height="1998" alt="old-404" src="https://github.com/user-attachments/assets/c5f044c4-aab6-40e9-aa11-6096ed9d2b42" /> --- ## After ### Light mode | 404 | 422 | 500 | |-----|-----|-----| | <img width="600" alt="new-404-light" src="https://github.com/user-attachments/assets/1826c812-0eb9-4219-bdd2-026c54b53123" /> | <img width="600" alt="new-422-light" src="https://github.com/user-attachments/assets/d72ffdbf-b61e-4f53-a16b-4ee81103124f" /> | <img width="600" alt="new-500-light" src="https://github.com/user-attachments/assets/81bbdb24-bfe7-43a1-b584-f37f71e3bded" /> | --- ### Dark mode | 404 | 422 | 500 | |-----|-----|-----| | <img width="600" alt="new-404-dark" src="https://github.com/user-attachments/assets/bd323915-bfb9-48c1-885d-96ff263b4ae0" /> | <img width="600" alt="new-422-dark" src="https://github.com/user-attachments/assets/dcb08eca-aee5-4e36-9690-f44da13e8d88" /> | <img width="600" alt="new-500-dark" src="https://github.com/user-attachments/assets/538c3c2c-b9dc-406c-9932-ff8897b64790" /> | --- ## Test plan Easier way to verify: - [ ] Visit `/404.html` directly to verify the 404 page - [ ] Visit `/422.html` directly to verify the 422 page - [ ] Visit `/500.html` directly to verify the 500 page Full verification: - [ ] Visit a non-existent route in production mode to verify the 404 page - [ ] Trigger a 422 error (e.g., invalid CSRF token) to verify the 422 page - [ ] Trigger a 500 error to verify the 500 page - [ ] Toggle system dark mode and verify all pages render correctly - [ ] Test on mobile viewport widths |
||
|
|
00ed074d72 |
fix: disable email transcript for free plans (#13509)
- Block email transcript functionality for accounts without a paid plan to prevent SES abuse. |
||
|
|
7b512bd00e |
fix: V2 Assignment service enhancements (#13036)
## Linear Ticket: https://linear.app/chatwoot/issue/CW-6081/review-feedback ## Description Assignment V2 Service Enhancements - Enable Assignment V2 on plan upgrade - Fix UI issue with fair distribution policy display - Add advanced assignment feature flag and enhance Assignment V2 capabilities ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? This has been tested using the UI. ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes auto-assignment execution paths, rate limiting defaults, and feature-flag gating (including premium plan behavior), which could affect which conversations get assigned and when. UI rewires inbox settings and policy flows, so regressions are possible around navigation/linking and feature visibility. > > **Overview** > **Adds a new premium `advanced_assignment` feature flag** and uses it to gate capacity/balanced assignment features in the UI (sidebar entry, settings routes, assignment-policy landing cards) and backend (Enterprise balanced selector + capacity filtering). `advanced_assignment` is marked premium, included in Business plan entitlements, and auto-synced in Enterprise accounts when `assignment_v2` is toggled. > > **Improves Assignment V2 policy UX** by adding an inbox-level “Conversation Assignment” section (behind `assignment_v2`) that can link/unlink an assignment policy, navigate to create/edit policy flows with `inboxId` query context, and show an inbox-link prompt after creating a policy. The policy form now defaults to enabled, disables the `balanced` option with a premium badge/message when unavailable, and inbox lists support click-to-navigate. > > **Tightens/adjusts auto-assignment behavior**: bulk assignment now requires `inbox.enable_auto_assignment?`, conversation ordering uses the attached `assignment_policy` priority, and rate limiting uses `assignment_policy` config with an infinite default limit while still tracking assignments. Tests and i18n strings are updated accordingly. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 23bc03bf75ee4376071e4d7fc7cd564c601d33d7. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Pranav <pranav@chatwoot.com> Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Shivam Mishra <scm.mymail@gmail.com> |
||
|
|
8f95fafff4 |
feat: Add a setting to keep conversations pending on bot failures (#13512)
Adds an account-level setting `keep_pending_on_bot_failure` to control whether conversations should move from pending to open when agent bot webhooks fail. Some users experience occasional message drops and don't want conversations to automatically reopen due to transient bot failures. This setting gives accounts control over that behavior. This is a temporary setting which will be removed in future once a proper fix for it is done, so it is not added in the UI. |
||
|
|
0ad47d87f4 |
fix: Use Faraday for Telegram document uploads to fix large file failures (#13397)
Fixes https://linear.app/chatwoot/issue/CW-6415/sending-large-attachments-11mb-via-telegram-channels-fails-with-http #### Issue Sending large attachments (~11MB) via Telegram channels fails with HTTP 502 (Bad Gateway) and 413 (Request Entity Too Large) errors. The issue is caused by HTTParty's built-in multipart encoding, which reads the entire file into an in-memory string before constructing the request body. For large files, this produces a malformed multipart request that Telegram's API proxy rejects. #### Solution Replace HTTParty with Faraday + multipart-post (both already available in the project) for the sendDocument multipart upload. The multipart-post gem streams file content directly from disk into the HTTP request, producing a correctly formed multipart body that Telegram accepts for large files. --------- Co-authored-by: Sojan Jose <sojan@pepalo.com> |
||
|
|
e65ea24360 | fix: Wrong assignee displayed after switching conversations (#13501) | ||
|
|
b252656984 |
fix: Prevent race condition in conversation dataFetched flag (#13492)
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com> |
||
|
|
6e397c7571 | fix: default model for captain assistant (#13496) | ||
|
|
4622560fac |
chore(dev): document codex worktree local setup (#13494)
This PR standardizes local Codex worktree usage with a simple dynamic-port workflow and ensures local-only artifacts stay out of version control. To reproduce: create a Codex worktree, run the setup script from `.codex/environments/environment.toml`, and verify that it generates per-worktree DB and port values along with a `Procfile.worktree` for Overmind. Changes included: - Add `.codex/` and `Procfile.worktree` to `.gitignore` - Document the Codex Worktree Workflow in `AGENTS.md`, outlining expected local setup conventions Tested locally by running the setup script with `CODEX_SKIP_INSTALL=1` and `CODEX_SKIP_DB_PREPARE=1`. Verified successful output, dynamic `FRONTEND_URL` / Vite port generation, and that `git diff` contains only the intended documentation and ignore updates. |
||
|
|
6632610e78 |
chore(deps): bump faraday from 2.13.1 to 2.14.1 (#13503)
Bumps [faraday](https://github.com/lostisland/faraday) from 2.13.1 to 2.14.1. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/lostisland/faraday/releases">faraday's releases</a>.</em></p> <blockquote> <h2>v2.14.1</h2> <h2>Security Note</h2> <p>This release contains a security fix, we recommend all users to upgrade as soon as possible. A Security Advisory with more details will be posted shortly.</p> <h2>What's Changed</h2> <ul> <li>Add comprehensive AI agent guidelines for Claude, Cursor, and GitHub Copilot by <a href="https://github.com/Copilot"><code>@Copilot</code></a> in <a href="https://redirect.github.com/lostisland/faraday/pull/1642">lostisland/faraday#1642</a></li> <li>Add RFC document for Options architecture refactoring plan by <a href="https://github.com/Copilot"><code>@Copilot</code></a> in <a href="https://redirect.github.com/lostisland/faraday/pull/1644">lostisland/faraday#1644</a></li> <li>Bump actions/checkout from 5 to 6 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/lostisland/faraday/pull/1655">lostisland/faraday#1655</a></li> <li>Explicit top-level namespace reference by <a href="https://github.com/c960657"><code>@c960657</code></a> in <a href="https://redirect.github.com/lostisland/faraday/pull/1657">lostisland/faraday#1657</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/Copilot"><code>@Copilot</code></a> made their first contribution in <a href="https://redirect.github.com/lostisland/faraday/pull/1642">lostisland/faraday#1642</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/lostisland/faraday/compare/v2.14.0...v2.14.1">https://github.com/lostisland/faraday/compare/v2.14.0...v2.14.1</a></p> <h2>v2.14.0</h2> <h2>What's Changed</h2> <h3>New features ✨</h3> <ul> <li>Use newer <code>UnprocessableContent</code> naming for 422 by <a href="https://github.com/tylerhunt"><code>@tylerhunt</code></a> in <a href="https://redirect.github.com/lostisland/faraday/pull/1638">lostisland/faraday#1638</a></li> </ul> <h3>Fixes 🐞</h3> <ul> <li>Convert strings to UTF-8 by <a href="https://github.com/c960657"><code>@c960657</code></a> in <a href="https://redirect.github.com/lostisland/faraday/pull/1624">lostisland/faraday#1624</a></li> <li>Fix <code>Response#to_hash</code> when response not finished yet by <a href="https://github.com/yykamei"><code>@yykamei</code></a> in <a href="https://redirect.github.com/lostisland/faraday/pull/1639">lostisland/faraday#1639</a></li> </ul> <h3>Misc/Docs 📄</h3> <ul> <li>Lint: use <code>filter_map</code> by <a href="https://github.com/olleolleolle"><code>@olleolleolle</code></a> in <a href="https://redirect.github.com/lostisland/faraday/pull/1637">lostisland/faraday#1637</a></li> <li>Bump <code>actions/checkout</code> from v4 to v5 by <a href="https://github.com/dependabot"><code>@dependabot</code></a>[bot] in <a href="https://redirect.github.com/lostisland/faraday/pull/1636">lostisland/faraday#1636</a></li> <li>Fixes documentation by <a href="https://github.com/dharamgollapudi"><code>@dharamgollapudi</code></a> in <a href="https://redirect.github.com/lostisland/faraday/pull/1635">lostisland/faraday#1635</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/c960657"><code>@c960657</code></a> made their first contribution in <a href="https://redirect.github.com/lostisland/faraday/pull/1624">lostisland/faraday#1624</a></li> <li><a href="https://github.com/dharamgollapudi"><code>@dharamgollapudi</code></a> made their first contribution in <a href="https://redirect.github.com/lostisland/faraday/pull/1635">lostisland/faraday#1635</a></li> <li><a href="https://github.com/tylerhunt"><code>@tylerhunt</code></a> made their first contribution in <a href="https://redirect.github.com/lostisland/faraday/pull/1638">lostisland/faraday#1638</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/lostisland/faraday/compare/v2.13.4...v2.14.0">https://github.com/lostisland/faraday/compare/v2.13.4...v2.14.0</a></p> <h2>v2.13.4</h2> <h2>What's Changed</h2> <ul> <li>Improve error handling logic and add missing test coverage by <a href="https://github.com/iMacTia"><code>@iMacTia</code></a> in <a href="https://redirect.github.com/lostisland/faraday/pull/1633">lostisland/faraday#1633</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/lostisland/faraday/compare/v2.13.3...v2.13.4">https://github.com/lostisland/faraday/compare/v2.13.3...v2.13.4</a></p> <h2>v2.13.3</h2> <h2>What's Changed</h2> <ul> <li>Fix type assumption in <code>Faraday::Error</code> by <a href="https://github.com/iMacTia"><code>@iMacTia</code></a> in <a href="https://redirect.github.com/lostisland/faraday/pull/1630">lostisland/faraday#1630</a></li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/lostisland/faraday/commit/16cbd38ef252d25dedf416a4d2510a2f3db10c87"><code>16cbd38</code></a> Version bump to 2.14.1</li> <li><a href="https://github.com/lostisland/faraday/commit/a6d3a3a0bf59c2ab307d0abd91bc126aef5561bc"><code>a6d3a3a</code></a> Merge commit from fork</li> <li><a href="https://github.com/lostisland/faraday/commit/b23f710d28c0dba169470f568df4017a1e8beea7"><code>b23f710</code></a> Explicit top-level namespace reference (<a href="https://redirect.github.com/lostisland/faraday/issues/1657">#1657</a>)</li> <li><a href="https://github.com/lostisland/faraday/commit/49ba4ac3a7359baed634c12a82386f6c8c717ea8"><code>49ba4ac</code></a> Bump actions/checkout from 5 to 6 (<a href="https://redirect.github.com/lostisland/faraday/issues/1655">#1655</a>)</li> <li><a href="https://github.com/lostisland/faraday/commit/51a49bc99d7df6f724d250d64771e1d710576df7"><code>51a49bc</code></a> Ensure Claude reads the guidelines and allow to plan in a gitignored .ai/PLAN...</li> <li><a href="https://github.com/lostisland/faraday/commit/894f65cab8f04bcf35e84a2dfd9fc0286dbce340"><code>894f65c</code></a> Add RFC document for Options architecture refactoring plan (<a href="https://redirect.github.com/lostisland/faraday/issues/1644">#1644</a>)</li> <li><a href="https://github.com/lostisland/faraday/commit/397e3ded0c5166313bb22f1c0221b36b6023fd0f"><code>397e3de</code></a> Add comprehensive AI agent guidelines for Claude, Cursor, and GitHub Copilot ...</li> <li><a href="https://github.com/lostisland/faraday/commit/d98c65cfc254ea2898386e4359428527122abec3"><code>d98c65c</code></a> Update Faraday-specific AI agent guidelines</li> <li><a href="https://github.com/lostisland/faraday/commit/56c18ecb718e30c5a3a0dea9bd2361912af9013c"><code>56c18ec</code></a> Add AI agent guidelines specific to Faraday repository</li> <li><a href="https://github.com/lostisland/faraday/commit/3201a42957d37efc968ee8834ba9b50ed5dde54a"><code>3201a42</code></a> Version bump to 2.14.0</li> <li>Additional commits viewable in <a href="https://github.com/lostisland/faraday/compare/v2.13.1...v2.14.1">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/chatwoot/chatwoot/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
bd732f1fa9 |
fix: search faqs in account language (#13428)
# Pull Request Template ## Description Reply suggestions uses `search_documentation`. While this is useful, there is a subtle bug, a user's message may be in a different language (say spanish) than the FAQs present (english). This results in embedding search in spanish and compared against english vectors, which results in poor retrieval and poor suggestions. Fixes # (issue) This PR fixes the above behaviour by making a small llm call translate the query before searching in the search documentation tool ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. before: <img width="894" height="157" alt="image" src="https://github.com/user-attachments/assets/83871ee5-511e-4432-8b99-39e803759f63" /> after: <img width="1149" height="294" alt="image" src="https://github.com/user-attachments/assets/f9617d7a-6d48-4ca1-ad1c-2181e16c1f3d" /> test on rails console: <img width="2094" height="380" alt="image" src="https://github.com/user-attachments/assets/159fdaa5-8808-49d2-be5d-304d69fa97f7" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules |
||
|
|
67112647e8 |
fix: escape special characters in Linear GraphQL queries (#13490)
Creating a Linear issue from Chatwoot fails with a GraphQL parse error
when the title, description, or search term contains double quotes. For
example, a description like `the sender is "Bot"` produces this broken
query:
```graphql
issueCreate(input: { description: "the sender is "Bot"" })
```
Linear's API rejects this with `Syntax Error: Expected ":", found
String`. This affects issue creation, issue linking, and issue search —
any flow where user-provided text is interpolated into a GraphQL query.
The `graphql_value` helper was only escaping newlines (`\n`) but not
quotes, backslashes, or other characters that are meaningful inside a
GraphQL string literal. On top of that, `issue_link` and `search_issue`
bypassed `graphql_value` entirely, using raw string interpolation
instead.
The fix replaces the manual `gsub` escaping with Ruby's `to_json`, which
produces a properly escaped, double-quoted string that handles all
special characters. This is a minimal, well-understood substitution —
`to_json` on a Ruby string returns a valid JSON string literal, which is
also a valid GraphQL string literal since GraphQL uses the same escaping
rules. The `issue_link` mutation and `search_issue` query are updated to
route their parameters through `graphql_value` instead of raw
interpolation.
The `team_entities_query` and `linked_issues` methods in `queries.rb`
also use raw interpolation, but their inputs are system-generated IDs
and URLs rather than user-provided text, so they're left as-is to keep
this change focused.
|
||
|
|
04c456e0a3 |
fix: handle 404 errors gracefully in avatar download job (#13491)
## Description Fixes `Avatar::AvatarFromUrlJob` logging 404 errors as ERROR when avatars don't exist ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Small logging-only behavior change that doesn’t affect attachment flow or persisted data beyond existing sync-attribute updates. > > **Overview** > Updates `Avatar::AvatarFromUrlJob` error handling to treat `Down::NotFound` (404/missing avatar URL) as a non-error: it now logs an INFO message instead of logging as ERROR. > > Other `Down::Error` failures continue to be logged as ERROR, and the job still runs `update_avatar_sync_attributes` in `ensure`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 675f41041ae3dd4ead6e0dee5f1586dcad9750cd. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> |
||
|
|
656ae41b24 |
fix(imap): handle IMAP parser/read errors without exception tracking (#13473)
When an IMAP server returns malformed or partial protocol responses, Inboxes::FetchImapEmailsJob can raise Net::IMAP::ResponseParseError, Net::IMAP::ResponseReadError, or Net::IMAP::ResponseTooLargeError. Previously these errors fell through to the generic StandardError handler and were captured repeatedly in Sentry. This PR updates app/jobs/inboxes/fetch_imap_emails_job.rb to treat those parser/read exceptions as handled IMAP failures by adding them to the existing IMAP/network rescue block, so they are logged and retried on the next scheduled run without exception tracking noise. fixes: https://chatwoot-p3.sentry.io/issues/7132037793/events/104fb9b4d80a4fb6ba3861c44c6c9b83/ How to reproduce: - Use an IMAP server/inbox that intermittently returns malformed or truncated protocol responses during search/fetch. - Run Inboxes::FetchImapEmailsJob for that channel and observe the raised parser/read exception. How this was tested: - bundle exec ruby -c app/jobs/inboxes/fetch_imap_emails_job.rb - bundle exec rubocop app/jobs/inboxes/fetch_imap_emails_job.rb |
||
|
|
f83415f299 |
fix(account-deletion): normalize deleted email suffix and handle collisions safely (#13472)
## Summary This PR fixes account deletion failures by changing how orphaned user emails are rewritten during `AccountDeletionService`. Ref: https://chatwoot-p3.sentry.io/issues/6715254765/events/e228a5d045ad47348d6c32448bc33b7a/ ## Changes (develop -> this branch) - Updated soft-delete email rewrite from: - `#{original_email}-deleted.com` - To deterministic value: - `#{user.id}@chatwoot-deleted.invalid` - Added reserved non-deliverable domain constant: - `@chatwoot-deleted.invalid` - Replaced the "other accounts" check from `count.zero?` to `exists?` (same behavior, cheaper query). - Updated service spec expectation to match deterministic email value and assert it differs from original email. ## Files changed - `app/services/account_deletion_service.rb` - `spec/services/account_deletion_service_spec.rb` ## How to verify - Run: `bundle exec rspec spec/services/account_deletion_service_spec.rb` - Run: `bundle exec rubocop app/services/account_deletion_service.rb spec/services/account_deletion_service_spec.rb` |
||
|
|
0a910c3763 | fix: Add email rate limiting to automation rule actions (#13474) | ||
|
|
6a7cbcf5ba |
fix: Fixes reply-to in WhatsApp Cloud API (#13467)
This change https://github.com/chatwoot/chatwoot/pull/13371 broke the functionality. When a user replies to a WhatsApp message, the reply context wasn't being properly stored in Chatwoot due to #13371 WhatsApp sends reply messages with a `context` field containing the original message ID: ```json { "messages": [{ "context": { "from": "phone_number", "id": "wamid.ORIGINAL_MESSAGE_ID" }, "from": "phone_number", "id": "wamid.REPLY_MESSAGE_ID", "text": { "body": "This is a reply" } }] } ``` However, the in_reply_to_external_id was being overridden when building the message because content_attributes was explicitly set to either { external_echo: true } or {}, which discarded the reply-to information. |
||
|
|
0e30e3c00a |
fix: add loading and silent retry to summary reports (#13455)
For large accounts, summary report queries can take several seconds to complete, often times hitting the 15-second production request timeout. The existing implementation silently swallows these failures and provides no feedback during loading. Users see stale data with no indication that a fetch is in progress, and if they interact with filters while a request is in flight, they trigger race conditions that can result in mismatched data being displayed. This is a UX-level fix for what is fundamentally a performance problem. While the underlying query performance is addressed separately, users need proper feedback either way ## Approach The PR adds three things: 1. A loading overlay on the table, to provide feedback on loading state 2. Disabled filter inputs during loading so that the user does not request new information that can cause race conditions in updating the store 3. Silent retry before showing an error. The retry exists because these queries often succeed on the second attempt—likely due to database query caching. Rather than immediately showing an error and forcing the user to manually retry, we do it automatically. If the second attempt also fails, we show a toast so the user knows something went wrong. The store previously caught and discarded errors entirely. It now rethrows them after resetting the loading flag, allowing components to handle failures as they see fit. ### Previews #### Double Retry and Error https://github.com/user-attachments/assets/c189b173-8017-44b7-9493-417d65582c95 #### Loading State https://github.com/user-attachments/assets/9f899c20-fbad-469b-93cc-f0d05d0853b0 --------- Co-authored-by: iamsivin <iamsivin@gmail.com> |
||
|
|
0d3b59fd9c | feat: Refactor reports filters (#13443) | ||
|
|
04e747cc02 |
chore: temporarily disable ProcessStaleContactsJob (#13462)
We've been seeing orphan conversations (conversations whose contact has been deleted) on high-frequency accounts. These orphans cause 500 errors when the API attempts to render conversation data, since the jbuilder partials expect a valid contact association. The `ProcessStaleContactsJob` triggers `RemoveStaleContactsService`, which uses `delete_all` to remove contacts. While the query only selects contacts without conversations, `delete_all` bypasses ActiveRecord callbacks and does not re-verify at deletion time. We suspect this creates a window where a new conversation can be created for a contact between query evaluation and the actual delete, leaving the conversation orphaned. **This is unverified — disabling the job is the experiment to confirm or rule out this theory.** This PR disables the job for a monitoring period. If orphan conversations stop appearing, we'll have confirmation and can work on a proper fix for the service. If they continue, we'll investigate other deletion paths. Stale contacts will accumulate while the job is disabled, but this is a controlled tradeoff — they are inert records with no user-facing impact, and can be cleaned up in bulk once we re-enable or fix the job. |
||
|
|
053b7774dd |
fix: Render all account limit fields (#13435)
## Bug Explanation - The Super Admin limits form renders inputs by iterating the keys of `account.limits`. - When `account.limits` was present, `AccountLimitsField#to_s` returned only that hash (no defaults). - On save, `SuperAdmin::AccountsController` compacts the limits hash, removing blank keys. - Result: if only one key (e.g., `agents`) was saved, the other keys were missing from the hash and their fields disappeared on the next render. ## Fix - Always start from a defaults hash of all expected limit keys and merge in any saved overrides. - This keeps the UI stable and ensures all limit inputs remain visible even when the stored hash is partial. - Upgraded meta_request to `0.8.5` to stop a dev‑only `SystemStackError` caused by JSON‑encoding ActiveRecord::Transaction in Rails 7.2. No production behavior changes. ## Reproduction Steps 1. In Super Admin, edit an account and set only `agents` in the limits; leave other limit fields blank and save. 2. Re-open the same account in Super Admin. 3. Observe that only `agents` is rendered and other limit fields are missing. ## Testing - Tested on UI --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> |