diff --git a/.claude/commands/dedupe.md b/.claude/commands/dedupe.md
new file mode 100644
index 0000000000..d58ce248cb
--- /dev/null
+++ b/.claude/commands/dedupe.md
@@ -0,0 +1,38 @@
+---
+allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh api:*), Bash(gh issue comment:*)
+description: Find duplicate GitHub issues
+---
+
+Find up to 3 likely duplicate issues for a given GitHub issue.
+
+To do this, follow these steps precisely:
+
+1. Use an agent to check if the Github issue (a) is closed, (b) does not need to be deduped (eg. because it is broad product feedback without a specific solution, or positive feedback), or (c) already has a duplicates comment that you made earlier. If so, do not proceed.
+2. Use an agent to view a Github issue, and ask the agent to return a summary of the issue
+3. Then, launch 5 parallel agents to search Github for duplicates of this issue, using diverse keywords and search approaches, using the summary from #1
+4. Next, feed the results from #1 and #2 into another agent, so that it can filter out false positives, that are likely not actually duplicates of the original issue. If there are no duplicates remaining, do not proceed.
+5. Finally, comment back on the issue with a list of up to three duplicate issues (or zero, if there are no likely duplicates)
+
+Notes (be sure to tell this to your agents, too):
+
+- Use `gh` to interact with Github, rather than web fetch
+- Do not use other tools, beyond `gh` (eg. don't use other MCP servers, file edit, etc.)
+- Make a todo list first
+- For your comment, follow the following format precisely (assuming for this example that you found 3 suspected duplicates):
+
+---
+
+Found 3 possible duplicate issues:
+
+1.
+2.
+3.
+
+This issue will be automatically closed as a duplicate in 3 days.
+
+- If your issue is a duplicate, please close it and 👍 the existing issue instead
+- To prevent auto-closure, add a comment or 👎 this comment
+
+> 🤖 Generated with Claude Code
+
+---
diff --git a/.claude/prompts/team-assignment.md b/.claude/prompts/team-assignment.md
new file mode 100644
index 0000000000..64259f9988
--- /dev/null
+++ b/.claude/prompts/team-assignment.md
@@ -0,0 +1,135 @@
+# Team Assignment Guide
+
+## Quick Reference by Name
+
+- **@arvinxx**: Last resort only, mention for priority:high issues, tool calling , mcp
+- **@canisminor1990**: Design, UI components, editor
+- **@tjx666**: Image/video generation, vision, cloud, documentation, TTS
+- **@ONLY-yours**: Performance, streaming, settings, general bugs, web platform, marketplace
+- **@RiverTwilight**: Knowledge base, files (KB-related), group chat
+- **@nekomeowww**: Memory, backend, deployment, DevOps
+- **@sudongyuer**: Mobile app (React Native)
+- **@sxjeru**: Model providers and configuration
+- **@cy948**: Auth Modules
+- **@rdmclin2**: Team workspace
+
+Quick reference for assigning issues based on labels.
+
+## Label to Team Member Mapping
+
+### Provider Labels (provider:\*)
+
+| Label | Owner | Notes |
+| ---------------- | ------- | -------------------------------------------- |
+| All `provider:*` | @sxjeru | Model configuration and provider integration |
+
+### Platform Labels (platform:\*)
+
+| Label | Owner | Notes |
+| ------------------ | ----------- | -------------------------------------- |
+| `platform:mobile` | @sudongyuer | React Native mobile app |
+| `platform:desktop` | @ONLY-yours | Electron desktop client (general) |
+| `platform:web` | @ONLY-yours | Web platform (unless specific feature) |
+
+### Feature Labels (feature:\*)
+
+| Label | Owner | Notes |
+| ------------------------ | --------------- | ----------------------------------------------------------------------- |
+| `feature:image` | @tjx666 | AI image generation |
+| `feature:dalle` | @tjx666 | DALL-E related |
+| `feature:vision` | @tjx666 | Vision/multimodal generation |
+| `feature:knowledge-base` | @RiverTwilight | Knowledge base and RAG |
+| `feature:files` | @RiverTwilight | File upload/management (when KB-related)
@ONLY-yours (general files) |
+| `feature:editor` | @canisminor1990 | Lobe Editor |
+| `feature:auth` | @cy948 | Authentication/authorization |
+| `feature:api` | @nekomeowww | Backend API |
+| `feature:streaming` | @arvinxx | Streaming response |
+| `feature:settings` | @ONLY-yours | Settings and configuration |
+| `feature:agent` | @ONLY-yours | Agent/Assistant |
+| `feature:topic` | @ONLY-yours | Topic/Conversation management |
+| `feature:thread` | @arvinxx | Thread/Subtopic |
+| `feature:marketplace` | @ONLY-yours | Agent marketplace |
+| `feature:tool` | @arvinxx | Tool calling |
+| `feature:mcp` | @arvinxx | MCP integration |
+| `feature:search` | @ONLY-yours | Search functionality |
+| `feature:tts` | @tjx666 | Text-to-speech |
+| `feature:export` | @ONLY-yours | Export functionality |
+| `feature:group-chat` | @RiverTwilight | Group chat functionality |
+| `feature:memory` | @nekomeowww | Memory feature |
+| `feature:team-workspace` | @rdmclin2 | Team workspace application |
+
+### Deployment Labels (deployment:\*)
+
+| Label | Owner | Notes |
+| ------------------ | ----------- | -------------------------- |
+| All `deployment:*` | @nekomeowww | Server/client/pglite modes |
+
+### Hosting Labels (hosting:\*)
+
+| Label | Owner | Notes |
+| ------------------- | ----------- | ---------------------- |
+| `hosting:cloud` | @tjx666 | Official LobeHub Cloud |
+| `hosting:self-host` | @nekomeowww | Self-hosting issues |
+| `hosting:vercel` | @nekomeowww | Vercel deployment |
+| `hosting:zeabur` | @nekomeowww | Zeabur deployment |
+| `hosting:railway` | @nekomeowww | Railway deployment |
+
+### Issue Type Labels
+
+| Label | Owner | Notes |
+| ------------------ | -------------------- | ---------------------------- |
+| 💄 Design | @canisminor1990 | Design and styling |
+| 📝 Documentation | @tjx666 | Documentation |
+| ⚡️ Performance | @ONLY-yours | Performance optimization |
+| 🐛 Bug | (depends on feature) | Assign based on other labels |
+| 🌠 Feature Request | (depends on feature) | Assign based on other labels |
+
+## Assignment Rules
+
+### Priority Order (apply in order)
+
+1. **Specific feature owner** - e.g., `feature:knowledge-base` → @RiverTwilight
+2. **Platform owner** - e.g., `platform:mobile` → @sudongyuer
+3. **Provider owner** - e.g., `provider:*` → @sxjeru
+4. **Component owner** - e.g., 💄 Design → @canisminor1990
+5. **Infrastructure owner** - e.g., `deployment:*` → @nekomeowww
+6. **General maintainer** - @ONLY-yours for general bugs/issues
+7. **Last resort** - @arvinxx (only if no clear owner)
+
+### Special Cases
+
+**Multiple labels with different owners:**
+
+- Mention the **most specific** feature owner first
+- Mention secondary owners if their input is valuable
+- Example: `feature:knowledge-base` + `deployment:server` → @RiverTwilight (primary), @nekomeowww (secondary)
+
+**Priority:high issues:**
+
+- Mention feature owner + @arvinxx
+- Example: `priority:high` + `feature:image` → @tjx666 @arvinxx
+
+**No clear owner:**
+
+- Assign to @ONLY-yours for general issues
+- Only mention @arvinxx if critical and truly unclear
+
+## Comment Templates
+
+**Single owner:**
+
+```
+@username - This is a [feature/component] issue. Please take a look.
+```
+
+**Multiple owners:**
+
+```
+@primary @secondary - This involves [features]. Please coordinate.
+```
+
+**High priority:**
+
+```
+@owner @arvinxx - High priority [feature] issue.
+```
diff --git a/.github/scripts/auto-close-duplicates.ts b/.github/scripts/auto-close-duplicates.ts
new file mode 100644
index 0000000000..b4fbebeb3d
--- /dev/null
+++ b/.github/scripts/auto-close-duplicates.ts
@@ -0,0 +1,260 @@
+#!/usr/bin/env bun
+
+declare global {
+ // @ts-ignore
+ // eslint-disable-next-line no-var
+ var process: {
+ env: Record;
+ };
+}
+
+interface GitHubIssue {
+ created_at: string;
+ number: number;
+ title: string;
+ user: { id: number };
+}
+
+interface GitHubComment {
+ body: string;
+ created_at: string;
+ id: number;
+ user: { id: number; type: string };
+}
+
+interface GitHubReaction {
+ content: string;
+ user: { id: number };
+}
+
+async function githubRequest(
+ endpoint: string,
+ token: string,
+ method: string = 'GET',
+ body?: any,
+): Promise {
+ const response = await fetch(`https://api.github.com${endpoint}`, {
+ headers: {
+ 'Accept': 'application/vnd.github.v3+json',
+ 'Authorization': `Bearer ${token}`,
+ 'User-Agent': 'auto-close-duplicates-script',
+ ...(body && { 'Content-Type': 'application/json' }),
+ },
+ method,
+ ...(body && { body: JSON.stringify(body) }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`GitHub API request failed: ${response.status} ${response.statusText}`);
+ }
+
+ return response.json();
+}
+
+function extractDuplicateIssueNumber(commentBody: string): number | null {
+ // Try to match #123 format first
+ let match = commentBody.match(/#(\d+)/);
+ if (match) {
+ return parseInt(match[1], 10);
+ }
+
+ // Try to match GitHub issue URL format: https://github.com/owner/repo/issues/123
+ match = commentBody.match(/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/);
+ if (match) {
+ return parseInt(match[1], 10);
+ }
+
+ return null;
+}
+
+async function closeIssueAsDuplicate(
+ owner: string,
+ repo: string,
+ issueNumber: number,
+ duplicateOfNumber: number,
+ token: string,
+): Promise {
+ await githubRequest(`/repos/${owner}/${repo}/issues/${issueNumber}`, token, 'PATCH', {
+ labels: ['duplicate'],
+ state: 'closed',
+ state_reason: 'duplicate',
+ });
+
+ await githubRequest(`/repos/${owner}/${repo}/issues/${issueNumber}/comments`, token, 'POST', {
+ body: `This issue has been automatically closed as a duplicate of #${duplicateOfNumber}.
+
+If this is incorrect, please re-open this issue or create a new one.
+
+🤖 Generated with [Claude Code](https://claude.ai/code)`,
+ });
+}
+
+async function autoCloseDuplicates(): Promise {
+ console.log('[DEBUG] Starting auto-close duplicates script');
+
+ const token = process.env.GITHUB_TOKEN;
+ if (!token) {
+ throw new Error('GITHUB_TOKEN environment variable is required');
+ }
+ console.log('[DEBUG] GitHub token found');
+
+ const owner = process.env.GITHUB_REPOSITORY_OWNER || 'lobehub';
+ const repo = process.env.GITHUB_REPOSITORY_NAME || 'lobe-chat';
+ console.log(`[DEBUG] Repository: ${owner}/${repo}`);
+
+ const threeDaysAgo = new Date();
+ threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
+ console.log(`[DEBUG] Checking for duplicate comments older than: ${threeDaysAgo.toISOString()}`);
+
+ console.log('[DEBUG] Fetching open issues created more than 3 days ago...');
+ const allIssues: GitHubIssue[] = [];
+ let page = 1;
+ const perPage = 100;
+
+ // eslint-disable-next-line no-constant-condition
+ while (true) {
+ const pageIssues: GitHubIssue[] = await githubRequest(
+ `/repos/${owner}/${repo}/issues?state=open&per_page=${perPage}&page=${page}`,
+ token,
+ );
+
+ if (pageIssues.length === 0) break;
+
+ // Filter for issues created more than 3 days ago
+ const oldEnoughIssues = pageIssues.filter(
+ (issue) => new Date(issue.created_at) <= threeDaysAgo,
+ );
+
+ allIssues.push(...oldEnoughIssues);
+ page++;
+
+ // Safety limit to avoid infinite loops
+ if (page > 20) break;
+ }
+
+ const issues = allIssues;
+ console.log(`[DEBUG] Found ${issues.length} open issues`);
+
+ let processedCount = 0;
+ let candidateCount = 0;
+
+ for (const issue of issues) {
+ processedCount++;
+ console.log(
+ `[DEBUG] Processing issue #${issue.number} (${processedCount}/${issues.length}): ${issue.title}`,
+ );
+
+ console.log(`[DEBUG] Fetching comments for issue #${issue.number}...`);
+ const comments: GitHubComment[] = await githubRequest(
+ `/repos/${owner}/${repo}/issues/${issue.number}/comments`,
+ token,
+ );
+ console.log(`[DEBUG] Issue #${issue.number} has ${comments.length} comments`);
+
+ const dupeComments = comments.filter(
+ (comment) =>
+ comment.body.includes('Found') &&
+ comment.body.includes('possible duplicate') &&
+ comment.user.type === 'Bot',
+ );
+ console.log(
+ `[DEBUG] Issue #${issue.number} has ${dupeComments.length} duplicate detection comments`,
+ );
+
+ if (dupeComments.length === 0) {
+ console.log(`[DEBUG] Issue #${issue.number} - no duplicate comments found, skipping`);
+ continue;
+ }
+
+ const lastDupeComment = dupeComments.at(-1);
+ // @ts-ignore
+ const dupeCommentDate = new Date(lastDupeComment.created_at);
+ console.log(
+ `[DEBUG] Issue #${
+ issue.number
+ } - most recent duplicate comment from: ${dupeCommentDate.toISOString()}`,
+ );
+
+ if (dupeCommentDate > threeDaysAgo) {
+ console.log(`[DEBUG] Issue #${issue.number} - duplicate comment is too recent, skipping`);
+ continue;
+ }
+ console.log(
+ `[DEBUG] Issue #${issue.number} - duplicate comment is old enough (${Math.floor(
+ (Date.now() - dupeCommentDate.getTime()) / (1000 * 60 * 60 * 24),
+ )} days)`,
+ );
+
+ const commentsAfterDupe = comments.filter(
+ (comment) => new Date(comment.created_at) > dupeCommentDate,
+ );
+ console.log(
+ `[DEBUG] Issue #${issue.number} - ${commentsAfterDupe.length} comments after duplicate detection`,
+ );
+
+ if (commentsAfterDupe.length > 0) {
+ console.log(
+ `[DEBUG] Issue #${issue.number} - has activity after duplicate comment, skipping`,
+ );
+ continue;
+ }
+
+ console.log(`[DEBUG] Issue #${issue.number} - checking reactions on duplicate comment...`);
+ const reactions: GitHubReaction[] = await githubRequest(
+ // @ts-ignore
+ `/repos/${owner}/${repo}/issues/comments/${lastDupeComment.id}/reactions`,
+ token,
+ );
+ console.log(
+ `[DEBUG] Issue #${issue.number} - duplicate comment has ${reactions.length} reactions`,
+ );
+
+ const authorThumbsDown = reactions.some(
+ (reaction) => reaction.user.id === issue.user.id && reaction.content === '-1',
+ );
+ console.log(
+ `[DEBUG] Issue #${issue.number} - author thumbs down reaction: ${authorThumbsDown}`,
+ );
+
+ if (authorThumbsDown) {
+ console.log(
+ `[DEBUG] Issue #${issue.number} - author disagreed with duplicate detection, skipping`,
+ );
+ continue;
+ }
+
+ // @ts-ignore
+ const duplicateIssueNumber = extractDuplicateIssueNumber(lastDupeComment.body);
+ if (!duplicateIssueNumber) {
+ console.log(
+ `[DEBUG] Issue #${issue.number} - could not extract duplicate issue number from comment, skipping`,
+ );
+ continue;
+ }
+
+ candidateCount++;
+ const issueUrl = `https://github.com/${owner}/${repo}/issues/${issue.number}`;
+
+ try {
+ console.log(
+ `[INFO] Auto-closing issue #${issue.number} as duplicate of #${duplicateIssueNumber}: ${issueUrl}`,
+ );
+ await closeIssueAsDuplicate(owner, repo, issue.number, duplicateIssueNumber, token);
+ console.log(
+ `[SUCCESS] Successfully closed issue #${issue.number} as duplicate of #${duplicateIssueNumber}`,
+ );
+ } catch (error) {
+ console.error(`[ERROR] Failed to close issue #${issue.number} as duplicate: ${error}`);
+ }
+ }
+
+ console.log(
+ `[DEBUG] Script completed. Processed ${processedCount} issues, found ${candidateCount} candidates for auto-close`,
+ );
+}
+
+// eslint-disable-next-line unicorn/prefer-top-level-await
+autoCloseDuplicates().catch(console.error);
+
+// Make it a module
+export {};
diff --git a/.github/workflows/claude-dedupe-issues.yml b/.github/workflows/claude-dedupe-issues.yml
new file mode 100644
index 0000000000..b2d51c22a8
--- /dev/null
+++ b/.github/workflows/claude-dedupe-issues.yml
@@ -0,0 +1,33 @@
+name: Claude Issue Dedupe
+description: Automatically dedupe GitHub issues using Claude Code
+on:
+ issues:
+ types: [opened]
+ workflow_dispatch:
+ inputs:
+ issue_number:
+ description: 'Issue number to process for duplicate detection'
+ required: true
+ type: string
+
+jobs:
+ claude-dedupe-issues:
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ permissions:
+ contents: read
+ issues: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v5
+ with:
+ fetch-depth: 1
+
+ - name: Run Claude Code slash command
+ uses: anthropics/claude-code-base-action@beta
+ with:
+ prompt: '/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}'
+ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
+ claude_env: |
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/claude-issue-triage.yml b/.github/workflows/claude-issue-triage.yml
new file mode 100644
index 0000000000..da4c7b9fed
--- /dev/null
+++ b/.github/workflows/claude-issue-triage.yml
@@ -0,0 +1,199 @@
+name: Claude Issue Triage
+description: Automatically triage GitHub issues using Claude Code
+on:
+ issues:
+ types: [opened, labeled]
+
+jobs:
+ triage-issue:
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ # Only run on issue opened, or when "trigger:triage" label is added
+ if: github.event.action == 'opened' || (github.event.action == 'labeled' && github.event.label.name == 'trigger:triage')
+ permissions:
+ contents: read
+ issues: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v5
+
+ - name: Copy team assignment guide
+ run: |
+ mkdir -p /tmp/claude-prompts
+ cp .claude/prompts/team-assignment.md /tmp/claude-prompts/
+
+ - name: Create triage prompt
+ run: |
+ mkdir -p /tmp/claude-prompts
+ cat > /tmp/claude-prompts/triage-prompt.md << 'EOF'
+ You're an issue triage assistant for GitHub issues. Your task is to analyze issues, apply appropriate labels, and mention the responsible team member.
+
+ REPOSITORY: ${{ github.repository }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+
+ ## WORKFLOW
+
+ ### Step 1: Get Available Labels
+ ```bash
+ gh label list --json name,description --limit 300
+ ```
+
+ ### Step 2: Get Issue Details
+ ```bash
+ gh issue view ${{ github.event.issue.number }} --json number,title,body,labels,comments
+ ```
+
+ ### Step 3: Read Team Assignment Guide
+ ```bash
+ cat /tmp/claude-prompts/team-assignment.md
+ ```
+
+ ### Step 4: Analyze and Select Labels
+
+ Extract information from the issue template:
+ - 📦 Platform field → platform:web/desktop/mobile
+ - 💻 Operating System → os:windows/macos/linux/ios
+ - 🌐 Browser → device:pc/mobile
+ - 📦 Deployment mode → deployment:server/client/pglite
+ - Platform (hosting) → hosting:cloud/self-host/vercel/zeabur/railway
+
+ **LABEL CATEGORIES:**
+
+ **a) Issue Type (REQUIRED - select ONE):**
+ - 🐛 Bug, 🌠 Feature Request, 💄 Design, 📝 Documentation, ⚡️ Performance
+
+ **b) Priority (select ONE if applicable):**
+ - priority:high - Critical, maintainer mentions "urgent"/"serious"/"critical"
+ - priority:medium - Important, affects multiple users
+ - priority:low - Nice to have, minor issues
+
+ **c) Platform (select ALL applicable):**
+ - platform:web, platform:desktop, platform:mobile
+
+ **d) Device (for platform:web, select ONE):**
+ - device:pc, device:mobile
+
+ **e) Operating System (select ALL applicable):**
+ - os:windows, os:macos, os:linux, os:ios
+
+ **f) Hosting Platform (select ONE):**
+ - hosting:cloud, hosting:self-host, hosting:vercel, hosting:zeabur, hosting:railway
+
+ **g) Deployment Mode (select ONE if mentioned):**
+ - deployment:server, deployment:client, deployment:pglite
+
+ **h) Model Provider (select ALL applicable):**
+ - provider:openai, provider:gemini, provider:claude, provider:deepseek
+ - provider:google, provider:ollama, provider:azure, provider:bedrock, provider:vertex
+
+ **i) Feature/Component (select ALL applicable):**
+ - feature:settings, feature:agent, feature:topic, feature:marketplace
+ - feature:streaming, feature:tool, feature:sync, feature:export
+ - feature:search, feature:auth, feature:files, feature:knowledge-base
+ - feature:tts, feature:vision, feature:mcp, feature:editor, feature:thread
+ - feature:image, feature:api, feature:dalle, feature:plugin, feature:markdown
+ - feature:group-chat, feature:memory, feature:team-workspace
+
+ **j) Workflow/Status:**
+ - Duplicate - Only if duplicate of OPEN issue
+ - needs-reproduction, good-first-issue, 🤔 Need Reproduce
+
+ **IMPORTANT RULES:**
+ - Read issue template fields carefully
+ - Check maintainer comments for priority/status
+ - Use ALL applicable labels from different categories
+ - Always use prefixes (feature:, provider:, os:, platform:, etc.)
+
+ ### Step 5: Apply Labels
+
+ Add labels:
+ ```bash
+ gh issue edit ${{ github.event.issue.number }} --add-label "label1,label2,label3"
+ ```
+
+ Remove "unconfirm" if adding other labels:
+ ```bash
+ gh issue edit ${{ github.event.issue.number }} --remove-label "unconfirm"
+ ```
+
+ **Execute the commands now.**
+
+ ### Step 6: Determine Team Member
+
+ Based on the team assignment guide and applied labels, determine who to mention.
+
+ **Priority Order:**
+ 1. Specific feature owner (feature:knowledge-base → @RiverTwilight)
+ 2. Platform owner (platform:mobile → @sudongyuer)
+ 3. Provider owner (provider:* → @sxjeru)
+ 4. Component owner (💄 Design → @canisminor1990)
+ 5. Infrastructure owner (deployment:* → @nekomeowww)
+ 6. General maintainer (@ONLY-yours)
+ 7. Last resort (@arvinxx)
+
+ **Special Cases:**
+ - Multiple owners: Mention primary + secondary
+ - priority:high: Mention owner + @arvinxx
+
+ ### Step 7: Post Comment
+
+ Format the comment (1-2 sentences):
+
+ **Single owner:**
+ ```
+ @username - This is a [feature/component] issue. Please take a look.
+ ```
+
+ **Multiple owners:**
+ ```
+ @user1 @user2 - This involves [features]. Please coordinate.
+ ```
+
+ **High priority:**
+ ```
+ @owner @arvinxx - High priority [feature] issue.
+ ```
+
+ Post the comment:
+ ```bash
+ gh issue comment ${{ github.event.issue.number }} --body "@username - [message]"
+ ```
+
+ **Execute the command now.**
+
+ ### Step 8: Output Summary
+
+ Log your reasoning (2-4 sentences):
+ - Labels applied and why
+ - Team member(s) mentioned and reason
+ - Key factors from issue template/comments
+
+ ## GUIDELINES
+
+ - Be thorough in analysis
+ - Use only labels from the provided list
+ - Extract info from issue template fields
+ - ALWAYS post a mention comment (unless no clear owner)
+ - Keep comments professional and brief
+ - Output reasoning to logs
+
+ **Start the triage process now.**
+ EOF
+
+ - name: Run Claude Code for Issue Triage
+ uses: anthropics/claude-code-base-action@beta
+ with:
+ prompt_file: /tmp/claude-prompts/triage-prompt.md
+ allowed_tools: "Bash(gh *),Read"
+ timeout_minutes: "5"
+ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
+ claude_env: |
+ GH_TOKEN: ${{ secrets.GH_TOKEN }}
+
+ - name: Remove trigger label
+ if: github.event.action == 'labeled' && github.event.label.name == 'trigger:triage'
+ run: |
+ gh issue edit ${{ github.event.issue.number }} --remove-label "trigger:triage"
+ env:
+ GH_TOKEN: ${{ secrets.GH_TOKEN }}
diff --git a/.github/workflows/desktop-pr-build.yml b/.github/workflows/desktop-pr-build.yml
index cf83b846a3..a04a52b01d 100644
--- a/.github/workflows/desktop-pr-build.yml
+++ b/.github/workflows/desktop-pr-build.yml
@@ -18,8 +18,8 @@ env:
jobs:
test:
name: Code quality check
- # 添加 PR label 触发条件,只有添加了 Build Desktop 标签的 PR 才会触发构建
- if: contains(github.event.pull_request.labels.*.name, 'Build Desktop')
+ # 添加 PR label 触发条件,只有添加了 trigger:build-desktop 标签的 PR 才会触发构建
+ if: contains(github.event.pull_request.labels.*.name, 'trigger:build-desktop')
runs-on: ubuntu-latest # 只在 ubuntu 上运行一次检查
steps:
- name: Checkout base
@@ -51,7 +51,7 @@ jobs:
version:
name: Determine version
# 与 test job 相同的触发条件
- if: contains(github.event.pull_request.labels.*.name, 'Build Desktop')
+ if: contains(github.event.pull_request.labels.*.name, 'trigger:build-desktop')
runs-on: ubuntu-latest
outputs:
# 输出版本信息,供后续 job 使用
diff --git a/.github/workflows/docker-database.yml b/.github/workflows/docker-database.yml
index a27ab29e04..a065ea741f 100644
--- a/.github/workflows/docker-database.yml
+++ b/.github/workflows/docker-database.yml
@@ -22,7 +22,7 @@ jobs:
# 添加 PR label 触发条件
if: |
(github.event_name == 'pull_request' &&
- contains(github.event.pull_request.labels.*.name, 'Build Docker')) ||
+ contains(github.event.pull_request.labels.*.name, 'trigger:build-docker')) ||
github.event_name != 'pull_request'
strategy:
diff --git a/.github/workflows/docker-pglite.yml b/.github/workflows/docker-pglite.yml
index 22084e6e0f..fc1900676e 100644
--- a/.github/workflows/docker-pglite.yml
+++ b/.github/workflows/docker-pglite.yml
@@ -22,7 +22,7 @@ jobs:
# 添加 PR label 触发条件
if: |
(github.event_name == 'pull_request' &&
- contains(github.event.pull_request.labels.*.name, 'Build Docker')) ||
+ contains(github.event.pull_request.labels.*.name, 'trigger:build-docker')) ||
github.event_name != 'pull_request'
strategy:
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 32468b4e35..ff211bf3bc 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -22,7 +22,7 @@ jobs:
# 添加 PR label 触发条件
if: |
(github.event_name == 'pull_request' &&
- contains(github.event.pull_request.labels.*.name, 'Build Docker')) ||
+ contains(github.event.pull_request.labels.*.name, 'trigger:build-docker')) ||
github.event_name != 'pull_request'
strategy:
diff --git a/.github/workflows/issue-auto-close-duplicates.yml b/.github/workflows/issue-auto-close-duplicates.yml
new file mode 100644
index 0000000000..9523e5a527
--- /dev/null
+++ b/.github/workflows/issue-auto-close-duplicates.yml
@@ -0,0 +1,30 @@
+name: Auto-close duplicate issues
+description: Auto-closes issues that are duplicates of existing issues
+on:
+ schedule:
+ - cron: "0 2 * * *"
+ workflow_dispatch:
+
+jobs:
+ auto-close-duplicates:
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ permissions:
+ contents: read
+ issues: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v5
+
+ - name: Setup Bun
+ uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: latest
+
+ - name: Auto-close duplicate issues
+ run: bun run .github/scripts/auto-close-duplicates.ts
+ env:
+ GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
+ GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
+ GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }}
diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml
index 06bf6fd693..5d3a6a989f 100644
--- a/.github/workflows/release-desktop-beta.yml
+++ b/.github/workflows/release-desktop-beta.yml
@@ -15,7 +15,7 @@ permissions: read-all
jobs:
test:
name: Code quality check
- # 添加 PR label 触发条件,只有添加了 Build Desktop 标签的 PR 才会触发构建
+ # 添加 PR label 触发条件,只有添加了 trigger:build-desktop 标签的 PR 才会触发构建
runs-on: ubuntu-latest # 只在 ubuntu 上运行一次检查
steps:
- name: Checkout base