From a183369e5568e7febc13fa10b609f8a3933e5b10 Mon Sep 17 00:00:00 2001 From: Daniel Gibbs Date: Sun, 3 May 2026 01:05:00 +0000 Subject: [PATCH] fix: remove AI triage workflow The GitHub Models API requires specialized authentication that is not available through secrets.GITHUB_TOKEN. The workflow consistently fails with 401 Unauthorized. The rule-based label automation (labeler.yml) continues to function correctly and provides automated labeling based on structured template fields. --- .github/workflows/ai-triage.yml | 229 -------------------------------- 1 file changed, 229 deletions(-) delete mode 100644 .github/workflows/ai-triage.yml diff --git a/.github/workflows/ai-triage.yml b/.github/workflows/ai-triage.yml deleted file mode 100644 index b48762749..000000000 --- a/.github/workflows/ai-triage.yml +++ /dev/null @@ -1,229 +0,0 @@ -name: AI Issue Triage -on: - issues: - types: - - opened - - edited - -permissions: - issues: write - contents: read - -jobs: - ai-triage: - if: github.repository_owner == 'GameServerManagers' - runs-on: ubuntu-latest - steps: - - name: Triage issue with GitHub Models - uses: actions/github-script@v7 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - script: | - const title = context.payload.issue.title || ''; - const body = context.payload.issue.body || ''; - const number = context.payload.issue.number; - const owner = context.repo.owner; - const repo = context.repo.repo; - const AI_MARKER = ''; - - function parseTriageResponse(raw) { - const input = (raw || '').trim(); - if (!input) return {}; - - const candidates = [input]; - const fenced = input.match(/```(?:json)?\s*([\s\S]*?)```/i); - if (fenced?.[1]) candidates.push(fenced[1].trim()); - - const firstBrace = input.indexOf('{'); - const lastBrace = input.lastIndexOf('}'); - if (firstBrace !== -1 && lastBrace > firstBrace) { - candidates.push(input.slice(firstBrace, lastBrace + 1)); - } - - for (const candidate of candidates) { - try { - return JSON.parse(candidate); - } catch (_err) { - // Continue trying fallbacks. - } - } - - return {}; - } - - // For short bodies, apply "needs: more info" label directly. - // Skip the AI call but still label the issue. - const isShortBody = body.trim().length < 80; - if (isShortBody) { - try { - await github.rest.issues.addLabels({ - owner, repo, issue_number: number, - labels: ['needs: more info'], - }); - } catch (err) { - console.log('Could not apply label for short body:', err.message); - } - return; - } - - // ── Call GitHub Models ──────────────────────────────────────── - let triage; - try { - const res = await fetch( - 'https://models.inference.ai.azure.com/chat/completions', - { - method: 'POST', - headers: { - 'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - model: 'gpt-4o-mini', - temperature: 0.1, - max_tokens: 400, - messages: [ - { - role: 'system', - content: - 'You are a triage assistant for LinuxGSM, an open-source ' + - 'Linux game server manager. Your role is to:\n' + - '1. Analyze issue quality (completeness, clarity)\n' + - '2. Extract game names mentioned in the issue, even if misspelled or abbreviated\n' + - '3. Suggest corrections for likely typos using fuzzy matching\n' + - '4. Respond ONLY with a valid JSON object — no markdown fences.\n\n' + - 'Common game name variations and typos you should recognize:\n' + - '- "Valhiem" → "Valheim"\n' + - '- "Rrust" → "Rust"\n' + - '- "Conterstrike" / "CS" / "CSGO" → "Counter-Strike: Global Offensive"\n' + - '- "Garrys" / "GMod" → "Garrys Mod"\n' + - '- "ARK" / "Ark" → "ARK: Survival Evolved"\n' + - '- "DayZ" / "Dayz" → "DayZ"\n' + - '- "Insurgency Sandstorm" / "Insurgency 2" → "Insurgency: Sandstorm"', - }, - { - role: 'user', - content: - `Title: ${title}\n\nBody:\n${body.slice(0, 3000)}\n\n` + - 'Respond with this JSON schema:\n' + - '{\n' + - ' "quality": "good" | "ok" | "poor",\n' + - ' "missing_info": ["list of specific missing fields"],\n' + - ' "detected_game": "canonical game name if one is mentioned, or null",\n' + - ' "game_confidence": "high" | "medium" | "low" | null,\n' + - ' "game_note": "correction suggestion if the user misspelled a game name, or empty string",\n' + - ' "comment": "one or two sentence note to the reporter, or empty string"\n' + - '}', - }, - ], - }), - } - ); - - if (!res.ok) { - console.log(`GitHub Models returned ${res.status} — skipping AI triage.`); - return; - } - - const data = await res.json(); - const raw = data.choices?.[0]?.message?.content || '{}'; - triage = parseTriageResponse(raw); - } catch (err) { - // Never fail the workflow if the AI call errors — it's advisory only. - console.log('AI triage skipped:', err.message); - return; - } - - if (!triage || typeof triage !== 'object') { - triage = {}; - } - - // ── Act on the result ──────────────────────────────────────── - const isPoor = triage.quality === 'poor'; - const missing = Array.isArray(triage.missing_info) ? triage.missing_info : []; - const hasIssues = isPoor || missing.length > 0; - - // Prepare labels to apply - const labelsToApply = []; - - // Check if a game was detected with high confidence - const detectedGame = triage.detected_game; - const gameConfidence = triage.game_confidence; - - if (detectedGame && gameConfidence === 'high') { - labelsToApply.push(`game: ${detectedGame}`); - } - - // Apply "needs: more info" label if quality issues detected - if (hasIssues) { - labelsToApply.push('needs: more info'); - } - - // Apply labels one-by-one so a single failure does not block all labels. - const uniqueLabels = [...new Set(labelsToApply)]; - for (const label of uniqueLabels) { - try { - await github.rest.issues.addLabels({ - owner, - repo, - issue_number: number, - labels: [label], - }); - } catch (err) { - console.log(`Could not apply label "${label}":`, err.message); - } - } - - // Post a comment only when there is something specific to say - const gameNote = triage.game_note || ''; - const reporterComment = triage.comment || ''; - - if (!hasIssues && !gameNote) return; - - const missingBlock = missing.length > 0 - ? `\n\n**Missing information:**\n${missing.map(m => `- ${m}`).join('\n')}` - : ''; - - const gameBlock = gameNote - ? `\n\n**Game name note:** ${gameNote}` - : ''; - - const triageCommentBody = - `${AI_MARKER}\n` + - `Thanks for opening this issue! 👋\n\n` + - `${reporterComment}` + - `${missingBlock}` + - `${gameBlock}\n\n` + - `_This note was generated automatically by AI triage and may not be perfect. ` + - `A maintainer will review shortly._`; - - try { - const comments = await github.rest.issues.listComments({ - owner, - repo, - issue_number: number, - per_page: 100, - }); - - const existingAiComment = comments.data.find( - (comment) => comment.user?.type === 'Bot' && comment.body?.includes(AI_MARKER) - ); - - if (existingAiComment) { - await github.rest.issues.updateComment({ - owner, - repo, - comment_id: existingAiComment.id, - body: triageCommentBody, - }); - } else { - await github.rest.issues.createComment({ - owner, - repo, - issue_number: number, - body: triageCommentBody, - }); - } - } catch (err) { - console.log('Could not post comment:', err.message); - }