{
	"issue": {
		"url": "https://api.github.com/repos/laurent22/joplin/issues/14756",
		"repository_url": "https://api.github.com/repos/laurent22/joplin",
		"labels_url": "https://api.github.com/repos/laurent22/joplin/issues/14756/labels{/name}",
		"comments_url": "https://api.github.com/repos/laurent22/joplin/issues/14756/comments",
		"events_url": "https://api.github.com/repos/laurent22/joplin/issues/14756/events",
		"html_url": "https://github.com/laurent22/joplin/pull/14756",
		"id": 4079043552,
		"node_id": "PR_kwDOBLftOs7KwIbR",
		"number": 14756,
		"title": "All: Fixes #14540: Handles duplicate tags conflict by overwriting local with remote during sync",
		"user": {
			"login": "fortyoneplustwo",
			"id": 26662250,
			"node_id": "MDQ6VXNlcjI2NjYyMjUw",
			"avatar_url": "https://avatars.githubusercontent.com/u/26662250?v=4",
			"gravatar_id": "",
			"url": "https://api.github.com/users/fortyoneplustwo",
			"html_url": "https://github.com/fortyoneplustwo",
			"followers_url": "https://api.github.com/users/fortyoneplustwo/followers",
			"following_url": "https://api.github.com/users/fortyoneplustwo/following{/other_user}",
			"gists_url": "https://api.github.com/users/fortyoneplustwo/gists{/gist_id}",
			"starred_url": "https://api.github.com/users/fortyoneplustwo/starred{/owner}{/repo}",
			"subscriptions_url": "https://api.github.com/users/fortyoneplustwo/subscriptions",
			"organizations_url": "https://api.github.com/users/fortyoneplustwo/orgs",
			"repos_url": "https://api.github.com/users/fortyoneplustwo/repos",
			"events_url": "https://api.github.com/users/fortyoneplustwo/events{/privacy}",
			"received_events_url": "https://api.github.com/users/fortyoneplustwo/received_events",
			"type": "User",
			"user_view_type": "public",
			"site_admin": false
		},
		"labels": [
			{
				"id": 519700741,
				"node_id": "MDU6TGFiZWw1MTk3MDA3NDE=",
				"url": "https://api.github.com/repos/laurent22/joplin/labels/bug",
				"name": "bug",
				"color": "cf353b",
				"default": true,
				"description": "It's a bug"
			},
			{
				"id": 1366675015,
				"node_id": "MDU6TGFiZWwxMzY2Njc1MDE1",
				"url": "https://api.github.com/repos/laurent22/joplin/labels/sync",
				"name": "sync",
				"color": "91ca55",
				"default": false,
				"description": "sync related issue"
			},
			{
				"id": 1367396396,
				"node_id": "MDU6TGFiZWwxMzY3Mzk2Mzk2",
				"url": "https://api.github.com/repos/laurent22/joplin/labels/tags",
				"name": "tags",
				"color": "91ca55",
				"default": false,
				"description": "tag related issue"
			}
		],
		"state": "closed",
		"locked": false,
		"assignees": [],
		"milestone": null,
		"comments": 5,
		"created_at": "2026-03-15T19:35:11Z",
		"updated_at": "2026-03-17T00:41:48Z",
		"closed_at": "2026-03-16T21:32:35Z",
		"assignee": null,
		"author_association": "NONE",
		"active_lock_reason": null,
		"draft": false,
		"pull_request": {
			"url": "https://api.github.com/repos/laurent22/joplin/pulls/14756",
			"html_url": "https://github.com/laurent22/joplin/pull/14756",
			"diff_url": "https://github.com/laurent22/joplin/pull/14756.diff",
			"patch_url": "https://github.com/laurent22/joplin/pull/14756.patch",
			"merged_at": null
		},
		"body": "## Problem\r\n\r\nSay we have two clients, `A` and `B`. If `B` creates a new tag that already exists in `A` but not in `B`, then the next sync will result in duplicate tags on both clients. \r\n\r\nThe problem arises due to the fact that when `B` creates a new tag, it has no knowledge that a tag with the same title already exists in `A`. Thus, it proceeds to create the tag with a fresh new id. When `B` syncs, both the local and remote tags are considered to be distinct from each other since they have different ids despite having the same title. And so we end up with duplicate tags in `B`. When `A` syncs, it also gets duplicate tags.\r\n\r\n## Solution\r\n\r\nOn sync, identify which remote tags have a duplicate local counterpart during the upload step. Handle the conflict by \r\n\r\n1. Overwriting the local tag with the remote tag.\r\n2. Untagging all local notes previously tagged with the local tag and re-tagging them the remote tag.\r\n\r\n### Reasoning\r\n\r\nIf a tag is present in `A` but not in `B` then it must appear in the delta results called from `B`. This is why we can reliably compare our never synced local tags to delta to check for duplicates.\r\n\r\n### Performance\r\n\r\n**Upload step**\r\n\r\nThe API call to delta is now also called at the beginning of the upload step and indexed. All queued downloads are awaited and also indexed. This is done to enable lookup of remote tags in O(1) to check for duplicates.\r\n\r\n**Delta step**\r\n\r\nTo prevent an additional API call in the delta step, we cache the result if it was previously called during the upload step.\r\n\r\n**Overall overhead**\r\n\r\n- **When the sync steps include delta**: this approach is just as efficient, except possibly when we await for the entire download queue to finish in the upload step. \r\n\r\n- **When the sync steps include delete and upload, but not delta**: we may incur overhead from both the call to delta and awaiting the download queue in the upload step.\r\n\r\n## Testing\r\n\r\n**Manual**\r\n\r\nThe change was verified manually by attempting to reproduce the error as discussed under issue #14540.\r\n\r\n- **Client 1**: Joplin CLI app\r\n- **Client 2**: Joplin desktop app (on Linux: Debian)\r\n\r\n1. Setup 2 Joplin clients with the default notes\r\n2. Make sure both clients are in sync\r\n3. On client 1, create the tag 'meetings', then click sync\r\n4. On client 2, before the client has a chance to sync again, create the tag 'meetings', then click sync\r\n\r\nOn sync completion, rather than ending up with 2 tags that each have the same title `meetings` with a count of 1, we get one tag `meetings` that has a count of 2. If we proceed to sync client 1 again, we get the same result.\r\n\r\n**Automated**\r\n\r\nTwo unit tests have been implemented to verify that the change works as intended. Particularly we check that\r\n\r\n1. The local tag has been overwritten with the remote tag.\r\n2. All related note tags have been amended to reflect the change in (1).\r\n\r\n\r\n\r\n\r\n\r\n",
		"closed_by": {
			"login": "laurent22",
			"id": 1285584,
			"node_id": "MDQ6VXNlcjEyODU1ODQ=",
			"avatar_url": "https://avatars.githubusercontent.com/u/1285584?v=4",
			"gravatar_id": "",
			"url": "https://api.github.com/users/laurent22",
			"html_url": "https://github.com/laurent22",
			"followers_url": "https://api.github.com/users/laurent22/followers",
			"following_url": "https://api.github.com/users/laurent22/following{/other_user}",
			"gists_url": "https://api.github.com/users/laurent22/gists{/gist_id}",
			"starred_url": "https://api.github.com/users/laurent22/starred{/owner}{/repo}",
			"subscriptions_url": "https://api.github.com/users/laurent22/subscriptions",
			"organizations_url": "https://api.github.com/users/laurent22/orgs",
			"repos_url": "https://api.github.com/users/laurent22/repos",
			"events_url": "https://api.github.com/users/laurent22/events{/privacy}",
			"received_events_url": "https://api.github.com/users/laurent22/received_events",
			"type": "User",
			"user_view_type": "public",
			"site_admin": false
		},
		"reactions": {
			"url": "https://api.github.com/repos/laurent22/joplin/issues/14756/reactions",
			"total_count": 0,
			"+1": 0,
			"-1": 0,
			"laugh": 0,
			"hooray": 0,
			"confused": 0,
			"heart": 0,
			"rocket": 0,
			"eyes": 0
		},
		"timeline_url": "https://api.github.com/repos/laurent22/joplin/issues/14756/timeline",
		"performed_via_github_app": null,
		"state_reason": null
	},
	"comments": [
		{
			"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/4063740960",
			"html_url": "https://github.com/laurent22/joplin/pull/14756#issuecomment-4063740960",
			"issue_url": "https://api.github.com/repos/laurent22/joplin/issues/14756",
			"id": 4063740960,
			"node_id": "IC_kwDOBLftOs7yN8Qg",
			"user": {
				"login": "github-actions[bot]",
				"id": 41898282,
				"node_id": "MDM6Qm90NDE4OTgyODI=",
				"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
				"gravatar_id": "",
				"url": "https://api.github.com/users/github-actions%5Bbot%5D",
				"html_url": "https://github.com/apps/github-actions",
				"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
				"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
				"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
				"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
				"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
				"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
				"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
				"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
				"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
				"type": "Bot",
				"user_view_type": "public",
				"site_admin": false
			},
			"created_at": "2026-03-15T19:35:23Z",
			"updated_at": "2026-03-17T00:41:48Z",
			"body": "****CLA Assistant Lite bot**** All contributors have signed the CLA  ✍️ ✅",
			"author_association": "CONTRIBUTOR",
			"reactions": {
				"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/4063740960/reactions",
				"total_count": 0,
				"+1": 0,
				"-1": 0,
				"laugh": 0,
				"hooray": 0,
				"confused": 0,
				"heart": 0,
				"rocket": 0,
				"eyes": 0
			},
			"performed_via_github_app": {
				"id": 15368,
				"client_id": "Iv1.05c79e9ad1f6bdfa",
				"slug": "github-actions",
				"node_id": "MDM6QXBwMTUzNjg=",
				"owner": {
					"login": "github",
					"id": 9919,
					"node_id": "MDEyOk9yZ2FuaXphdGlvbjk5MTk=",
					"avatar_url": "https://avatars.githubusercontent.com/u/9919?v=4",
					"gravatar_id": "",
					"url": "https://api.github.com/users/github",
					"html_url": "https://github.com/github",
					"followers_url": "https://api.github.com/users/github/followers",
					"following_url": "https://api.github.com/users/github/following{/other_user}",
					"gists_url": "https://api.github.com/users/github/gists{/gist_id}",
					"starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}",
					"subscriptions_url": "https://api.github.com/users/github/subscriptions",
					"organizations_url": "https://api.github.com/users/github/orgs",
					"repos_url": "https://api.github.com/users/github/repos",
					"events_url": "https://api.github.com/users/github/events{/privacy}",
					"received_events_url": "https://api.github.com/users/github/received_events",
					"type": "Organization",
					"user_view_type": "public",
					"site_admin": false
				},
				"name": "GitHub Actions",
				"description": "Automate your workflow from idea to production",
				"external_url": "https://help.github.com/en/actions",
				"html_url": "https://github.com/apps/github-actions",
				"created_at": "2018-07-30T09:30:17Z",
				"updated_at": "2026-05-05T14:51:38Z",
				"permissions": {
					"actions": "write",
					"administration": "read",
					"artifact_metadata": "write",
					"attestations": "write",
					"checks": "write",
					"code_quality": "write",
					"contents": "write",
					"copilot_requests": "write",
					"deployments": "write",
					"discussions": "write",
					"issues": "write",
					"merge_queues": "write",
					"metadata": "read",
					"models": "read",
					"packages": "write",
					"pages": "write",
					"pull_requests": "write",
					"repository_hooks": "write",
					"repository_projects": "write",
					"security_events": "write",
					"statuses": "write",
					"vulnerability_alerts": "read"
				},
				"events": [
					"branch_protection_rule",
					"check_run",
					"check_suite",
					"create",
					"delete",
					"deployment",
					"deployment_status",
					"discussion",
					"discussion_comment",
					"fork",
					"gollum",
					"issues",
					"issue_comment",
					"label",
					"merge_group",
					"milestone",
					"page_build",
					"public",
					"pull_request",
					"pull_request_review",
					"pull_request_review_comment",
					"push",
					"registry_package",
					"release",
					"repository",
					"repository_dispatch",
					"status",
					"watch",
					"workflow_dispatch",
					"workflow_run"
				]
			}
		},
		{
			"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/4063741477",
			"html_url": "https://github.com/laurent22/joplin/pull/14756#issuecomment-4063741477",
			"issue_url": "https://api.github.com/repos/laurent22/joplin/issues/14756",
			"id": 4063741477,
			"node_id": "IC_kwDOBLftOs7yN8Yl",
			"user": {
				"login": "coderabbitai[bot]",
				"id": 136622811,
				"node_id": "BOT_kgDOCCSy2w",
				"avatar_url": "https://avatars.githubusercontent.com/in/347564?v=4",
				"gravatar_id": "",
				"url": "https://api.github.com/users/coderabbitai%5Bbot%5D",
				"html_url": "https://github.com/apps/coderabbitai",
				"followers_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/followers",
				"following_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}",
				"gists_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}",
				"starred_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}",
				"subscriptions_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions",
				"organizations_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/orgs",
				"repos_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/repos",
				"events_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}",
				"received_events_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/received_events",
				"type": "Bot",
				"user_view_type": "public",
				"site_admin": false
			},
			"created_at": "2026-03-15T19:35:41Z",
			"updated_at": "2026-03-15T20:46:26Z",
			"body": "<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n<details>\n<summary>📝 Walkthrough</summary>\n\n## Walkthrough\n\nDelta synchronisation has been refactored to use a deltaPages structure for more explicit paging and content coordination. Tag duplicate detection and resolution have been added during delta processing, including logic to detect duplicate tags by title and reconcile them through untagging and re-tagging. A new waitForAllResults() method has been introduced to TaskQueue, and a new ResolveDuplicateTag sync action type has been added to support conflict resolution.\n\n## Changes\n\n|Cohort / File(s)|Summary|\n|---|---|\n|**Synchronizer Delta and Tag Conflict Resolution** <br> `packages/lib/Synchronizer.ts`|Major refactoring of delta processing orchestration with new deltaPages and remotesIndex structures for coordinating content downloads. Added duplicate tag detection and resolution logic during delta processing, including remoteTagsIndex construction and ResolveDuplicateTag action handling. Error handling improvements for locked sync targets using structured JoplinError. Enhanced logging and progress messaging throughout delta fetch and processing flows.|\n|**Task Queue Enhancement** <br> `packages/lib/TaskQueue.ts`|Added public `waitForAllResults()` method that returns aggregated task results once processing completes, mirroring existing `waitForAll()` pattern.|\n|**Sync Action Types** <br> `packages/lib/services/synchronizer/utils/types.ts`|Added `ResolveDuplicateTag` enum value to `SyncAction` type to support tag conflict resolution actions.|\n|**Tag Conflict Resolution Tests** <br> `packages/lib/services/synchronizer/Synchronizer.tags.test.ts`|Added two test cases validating tag duplicate replacement and note tag ID updates when local tags match remote tags by title.|\n\n## Sequence Diagram\n\n```mermaid\nsequenceDiagram\n    participant S as Synchronizer\n    participant DP as Delta Processor\n    participant TR as Tag Resolver\n    participant NI as Note Items\n\n    S->>DP: Fetch delta pages\n    DP-->>S: deltaPages with remote items\n    \n    S->>TR: Build remoteTagsIndex from delta\n    TR-->>S: remoteTagsIndex by title\n    \n    S->>DP: Process delta items with index\n    DP->>DP: Check for tag duplicates by title\n    \n    alt Duplicate Tag Found\n        DP->>TR: Detect local vs remote tag mismatch\n        TR->>NI: Untag notes from local tag\n        TR->>NI: Re-tag notes with remote tag\n        TR-->>DP: ResolveDuplicateTag action\n        DP->>S: Queue delta step for re-upload\n    else No Duplicate\n        DP->>S: Continue normal sync\n    end\n    \n    S-->>S: Complete synchronisation\n```\n\n## Possibly Related PRs\n\n- **PR `#14388`**: Modifies delta-processing logic in Synchronizer.ts for handling how remote items are mapped and processed during synchronisation.\n\n## Suggested Labels\n\n`bug`, `sync`, `tags`\n\n</details>\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 4</summary>\n\n<details>\n<summary>✅ Passed checks (4 passed)</summary>\n\n|               Check name              | Status   | Explanation                                                                                                                                                                                              |\n| :-----------------------------------: | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n|              Title check              | ✅ Passed | The title accurately summarizes the main change: handling duplicate tags conflict by overwriting local with remote during sync, which directly corresponds to the primary objective of the pull request. |\n|           Docstring Coverage          | ✅ Passed | Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.                                                                                                                     |\n| Pr Description Must Follow Guidelines | ✅ Passed | The pull request description includes all three required sections: problem statement, solution approach, and test plan.                                                                                  |\n|           Description check           | ✅ Passed | The pull request description clearly explains the duplicate tag problem, the solution approach, performance considerations, and testing methodology.                                                     |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"07f1e7d6-8a8e-4e23-9900-8731c2c87f58\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Post copyable unit tests in a comment\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- pr_review_plan_action_start -->\n\n<details>\n<summary>📝 Coding Plan</summary>\n\n- [ ] <!-- {\"checkboxId\": \"6ad8a4e1-0b3a-4ea2-9b5b-d82c1f47d1f2\"} --> Generate coding plan for human review comments\n\n</details>\n\n<!-- pr_review_plan_action_end -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=laurent22/joplin&utm_content=14756)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- [encoded data, 6996 characters] -->\n\n<!-- internal state end -->\n<!-- usage_tips_start -->\n\n> [!TIP]\n> <details>\n> <summary>CodeRabbit can use OpenGrep to find security vulnerabilities and bugs across 17+ programming languages.</summary>\n> \n> OpenGrep is compatible with Semgrep configurations. Add an `opengrep.yml` or `semgrep.yml` [configuration file](https://github.com/opengrep/opengrep) to your project to enable OpenGrep analysis.\n> \n> </details>\n\n<!-- usage_tips_end -->",
			"author_association": "CONTRIBUTOR",
			"reactions": {
				"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/4063741477/reactions",
				"total_count": 0,
				"+1": 0,
				"-1": 0,
				"laugh": 0,
				"hooray": 0,
				"confused": 0,
				"heart": 0,
				"rocket": 0,
				"eyes": 0
			},
			"performed_via_github_app": {
				"id": 347564,
				"client_id": "Iv1.6aaafe4fe882736b",
				"slug": "coderabbitai",
				"node_id": "A_kwHOB96YWc4ABU2s",
				"owner": {
					"login": "coderabbitai",
					"id": 132028505,
					"node_id": "O_kgDOB96YWQ",
					"avatar_url": "https://avatars.githubusercontent.com/u/132028505?v=4",
					"gravatar_id": "",
					"url": "https://api.github.com/users/coderabbitai",
					"html_url": "https://github.com/coderabbitai",
					"followers_url": "https://api.github.com/users/coderabbitai/followers",
					"following_url": "https://api.github.com/users/coderabbitai/following{/other_user}",
					"gists_url": "https://api.github.com/users/coderabbitai/gists{/gist_id}",
					"starred_url": "https://api.github.com/users/coderabbitai/starred{/owner}{/repo}",
					"subscriptions_url": "https://api.github.com/users/coderabbitai/subscriptions",
					"organizations_url": "https://api.github.com/users/coderabbitai/orgs",
					"repos_url": "https://api.github.com/users/coderabbitai/repos",
					"events_url": "https://api.github.com/users/coderabbitai/events{/privacy}",
					"received_events_url": "https://api.github.com/users/coderabbitai/received_events",
					"type": "Organization",
					"user_view_type": "public",
					"site_admin": false
				},
				"name": "coderabbitai",
				"description": "# Transforming Code Reviews with AI\r\n\r\n## Features\r\n\r\n**Automated Reviews**: Continuous reviews of the pull requests including incremental commits. \r\n\r\n**Summarization**: Generates high-level summary and a technical walkthrough of the PR changes. \r\n\r\n**Line-by-line review**: Provides line-by-line suggestions committable with one click.\r\n\r\n**Codebase verification**:  Verifies the impact on the overall codebase and identifies missing changes.\r\n\r\n**Insights into your code**:  Ask any questions on your codebase within the pull request \r\n\r\n**Chat about your code** : Chat with the bot around your code. The more you chat, the smarter it gets.\r\n\r\n**Issue Validation**:  Validates the PR against the linked issues and identifies other related issues \r\n\r\n\r\n\r\n",
				"external_url": "https://coderabbit.ai?utm_source=cr_app&utm_medium=github",
				"html_url": "https://github.com/apps/coderabbitai",
				"created_at": "2023-06-14T15:47:27Z",
				"updated_at": "2026-03-14T07:58:44Z",
				"permissions": {
					"actions": "read",
					"checks": "write",
					"contents": "write",
					"discussions": "read",
					"issues": "write",
					"members": "read",
					"metadata": "read",
					"pull_requests": "write",
					"statuses": "write"
				},
				"events": [
					"issues",
					"issue_comment",
					"label",
					"organization",
					"pull_request",
					"pull_request_review",
					"pull_request_review_comment",
					"pull_request_review_thread",
					"release",
					"repository"
				]
			}
		},
		{
			"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/4063743542",
			"html_url": "https://github.com/laurent22/joplin/pull/14756#issuecomment-4063743542",
			"issue_url": "https://api.github.com/repos/laurent22/joplin/issues/14756",
			"id": 4063743542,
			"node_id": "IC_kwDOBLftOs7yN842",
			"user": {
				"login": "fortyoneplustwo",
				"id": 26662250,
				"node_id": "MDQ6VXNlcjI2NjYyMjUw",
				"avatar_url": "https://avatars.githubusercontent.com/u/26662250?v=4",
				"gravatar_id": "",
				"url": "https://api.github.com/users/fortyoneplustwo",
				"html_url": "https://github.com/fortyoneplustwo",
				"followers_url": "https://api.github.com/users/fortyoneplustwo/followers",
				"following_url": "https://api.github.com/users/fortyoneplustwo/following{/other_user}",
				"gists_url": "https://api.github.com/users/fortyoneplustwo/gists{/gist_id}",
				"starred_url": "https://api.github.com/users/fortyoneplustwo/starred{/owner}{/repo}",
				"subscriptions_url": "https://api.github.com/users/fortyoneplustwo/subscriptions",
				"organizations_url": "https://api.github.com/users/fortyoneplustwo/orgs",
				"repos_url": "https://api.github.com/users/fortyoneplustwo/repos",
				"events_url": "https://api.github.com/users/fortyoneplustwo/events{/privacy}",
				"received_events_url": "https://api.github.com/users/fortyoneplustwo/received_events",
				"type": "User",
				"user_view_type": "public",
				"site_admin": false
			},
			"created_at": "2026-03-15T19:36:46Z",
			"updated_at": "2026-03-15T19:36:46Z",
			"body": "I have read the CLA Document and I hereby sign the CLA",
			"author_association": "NONE",
			"reactions": {
				"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/4063743542/reactions",
				"total_count": 0,
				"+1": 0,
				"-1": 0,
				"laugh": 0,
				"hooray": 0,
				"confused": 0,
				"heart": 0,
				"rocket": 0,
				"eyes": 0
			},
			"performed_via_github_app": null
		},
		{
			"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/4064116286",
			"html_url": "https://github.com/laurent22/joplin/pull/14756#issuecomment-4064116286",
			"issue_url": "https://api.github.com/repos/laurent22/joplin/issues/14756",
			"id": 4064116286,
			"node_id": "IC_kwDOBLftOs7yPX4-",
			"user": {
				"login": "mrjo118",
				"id": 6963004,
				"node_id": "MDQ6VXNlcjY5NjMwMDQ=",
				"avatar_url": "https://avatars.githubusercontent.com/u/6963004?v=4",
				"gravatar_id": "",
				"url": "https://api.github.com/users/mrjo118",
				"html_url": "https://github.com/mrjo118",
				"followers_url": "https://api.github.com/users/mrjo118/followers",
				"following_url": "https://api.github.com/users/mrjo118/following{/other_user}",
				"gists_url": "https://api.github.com/users/mrjo118/gists{/gist_id}",
				"starred_url": "https://api.github.com/users/mrjo118/starred{/owner}{/repo}",
				"subscriptions_url": "https://api.github.com/users/mrjo118/subscriptions",
				"organizations_url": "https://api.github.com/users/mrjo118/orgs",
				"repos_url": "https://api.github.com/users/mrjo118/repos",
				"events_url": "https://api.github.com/users/mrjo118/events{/privacy}",
				"received_events_url": "https://api.github.com/users/mrjo118/received_events",
				"type": "User",
				"user_view_type": "public",
				"site_admin": false
			},
			"created_at": "2026-03-15T23:02:41Z",
			"updated_at": "2026-03-15T23:02:41Z",
			"body": "This is a very rare bug. Making major changes to the core synchronisation logic is really not worth it to try to fix this",
			"author_association": "COLLABORATOR",
			"reactions": {
				"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/4064116286/reactions",
				"total_count": 0,
				"+1": 0,
				"-1": 0,
				"laugh": 0,
				"hooray": 0,
				"confused": 0,
				"heart": 0,
				"rocket": 0,
				"eyes": 0
			},
			"performed_via_github_app": null
		},
		{
			"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/4070715444",
			"html_url": "https://github.com/laurent22/joplin/pull/14756#issuecomment-4070715444",
			"issue_url": "https://api.github.com/repos/laurent22/joplin/issues/14756",
			"id": 4070715444,
			"node_id": "IC_kwDOBLftOs7yojA0",
			"user": {
				"login": "laurent22",
				"id": 1285584,
				"node_id": "MDQ6VXNlcjEyODU1ODQ=",
				"avatar_url": "https://avatars.githubusercontent.com/u/1285584?v=4",
				"gravatar_id": "",
				"url": "https://api.github.com/users/laurent22",
				"html_url": "https://github.com/laurent22",
				"followers_url": "https://api.github.com/users/laurent22/followers",
				"following_url": "https://api.github.com/users/laurent22/following{/other_user}",
				"gists_url": "https://api.github.com/users/laurent22/gists{/gist_id}",
				"starred_url": "https://api.github.com/users/laurent22/starred{/owner}{/repo}",
				"subscriptions_url": "https://api.github.com/users/laurent22/subscriptions",
				"organizations_url": "https://api.github.com/users/laurent22/orgs",
				"repos_url": "https://api.github.com/users/laurent22/repos",
				"events_url": "https://api.github.com/users/laurent22/events{/privacy}",
				"received_events_url": "https://api.github.com/users/laurent22/received_events",
				"type": "User",
				"user_view_type": "public",
				"site_admin": false
			},
			"created_at": "2026-03-16T21:32:35Z",
			"updated_at": "2026-03-16T21:32:35Z",
			"body": "Thanks for the submission.\r\n\r\nWhile this PR targets an existing issue, the proposed solution is not suitable for the project in its current form. Bringing it up to an acceptable standard would require significant redesign and guidance.\r\n\r\nFor future contributions, please ensure changes are well-aligned with the existing architecture and sufficiently tested.\r\n\r\nPlease make sure you read our guidelines: https://github.com/joplin/gsoc\r\n\r\nAnd our AI policy: https://github.com/joplin/gsoc/blob/master/ai_policy.md",
			"author_association": "OWNER",
			"reactions": {
				"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/4070715444/reactions",
				"total_count": 0,
				"+1": 0,
				"-1": 0,
				"laugh": 0,
				"hooray": 0,
				"confused": 0,
				"heart": 0,
				"rocket": 0,
				"eyes": 0
			},
			"performed_via_github_app": null
		}
	]
}