From 2db042fe5a692db0cb0c0d8c4575fbdab6a31f99 Mon Sep 17 00:00:00 2001 From: merlin Date: Tue, 19 May 2026 21:17:48 +0800 Subject: [PATCH 1/2] feat(issue-feed): simplify to new/reopen only, remove project-group and IssueTriage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove project_group_id input and the per-repo group notification. Only the shared issue-feed group receives messages going forward. - Remove IssueTriage bot mention/trigger (deprecated in favour of IssueTriage monitoring the issue-feed group directly). - Remove issue_labels input โ€” label display is irrelevant when the labeled event is no longer forwarded. - Add early exit for any event_action not in (opened, reopened) so callers don't need to filter; the reusable itself enforces the policy. - Remove mention_uids from send() โ€” no longer needed. - Remove require_group_id() helper โ€” no longer needed. --- .github/workflows/octo-issue-feed.yml | 102 +++++++++----------------- 1 file changed, 36 insertions(+), 66 deletions(-) diff --git a/.github/workflows/octo-issue-feed.yml b/.github/workflows/octo-issue-feed.yml index b2c3936..91a01d5 100644 --- a/.github/workflows/octo-issue-feed.yml +++ b/.github/workflows/octo-issue-feed.yml @@ -1,5 +1,6 @@ -# Reusable workflow: notify Octo IM when an issue event occurs. +# Reusable workflow: notify Octo IM when an issue is opened or reopened. # Called from per-repo caller workflows. +# Only new/reopen events are forwarded; close, label, and other events are ignored. name: Octo Issue Feed (reusable) on: @@ -20,20 +21,14 @@ on: issue_author: type: string required: true - issue_labels: - type: string - default: '[]' event_action: type: string required: true - project_group_id: - type: string - required: true api_base_url: type: string required: false default: 'https://im.deepminer.com.cn/api' - description: 'Octo IM API base URL. Only the production endpoint is allowlisted; any other value will cause the workflow to fail.' + description: 'Octo IM API base URL. Only the production endpoint is allowlisted.' secrets: OCTO_BOT_TOKEN: required: true @@ -45,7 +40,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - name: Notify Octo IM + - name: Notify Octo IM โ€” issue-feed env: OCTO_BOT_TOKEN: ${{ secrets.OCTO_BOT_TOKEN }} REPO_NAME: ${{ inputs.repo_name }} @@ -53,68 +48,54 @@ jobs: ISSUE_TITLE: ${{ inputs.issue_title }} ISSUE_URL: ${{ inputs.issue_url }} ISSUE_AUTHOR: ${{ inputs.issue_author }} - ISSUE_LABELS: ${{ inputs.issue_labels }} EVENT_ACTION: ${{ inputs.event_action }} - PROJECT_GROUP_ID: ${{ inputs.project_group_id }} API_BASE_URL: ${{ inputs.api_base_url }} run: | python3 - << 'PYEOF' import os, json, re, sys, time, urllib.request, urllib.error - - + + def require_env(name): val = os.environ.get(name, '').strip() if not val: print(f'ERROR: required environment variable {name} is missing or empty') sys.exit(2) return val - - + + def sanitize_text(s, max_len=300): - """Strip control characters (CR, LF, tabs, etc.) to prevent IM message injection.""" + """Strip control characters to prevent IM message injection.""" s = str(s or '') - # Strip all C0 control characters (U+0000-U+001F) and DEL (U+007F) s = re.sub(r'[\x00-\x1f\x7f]', ' ', s) return s[:max_len] - - - def require_group_id(name): - """Validate format only (32-char hex); this is not an authorization check.""" - val = require_env(name) - if not re.fullmatch(r'[0-9a-f]{32}', val): - print(f'ERROR: {name} must be a 32-char lowercase hex group id') - sys.exit(2) - return val def require_repo_name(name): - """Validate repo name to prevent path traversal in GitHub API URLs.""" + """Validate repo name to prevent path traversal.""" val = require_env(name) if not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9._-]{0,99}', val) or val in {'.', '..'}: print(f'ERROR: {name} contains invalid characters: {val!r}') sys.exit(2) return val - - + + action = require_env('EVENT_ACTION') - emoji = {'opened': '๐Ÿ†•', 'closed': 'โœ…', 'reopened': '๐Ÿ”„', 'labeled': '๐Ÿท๏ธ'}.get(action, 'โ„น๏ธ') - - try: - labels = [sanitize_text(l, max_len=64) for l in json.loads(os.environ.get('ISSUE_LABELS', '[]'))] - labels_part = ' ยท ๐Ÿท๏ธ ' + ', '.join(labels) if labels else '' - except Exception: - labels_part = '' - - repo = require_repo_name('REPO_NAME') - num = require_env('ISSUE_NUMBER') - title = sanitize_text(require_env('ISSUE_TITLE'), max_len=300) - url = require_env('ISSUE_URL') + + # Only process new and reopened issues; ignore all other events. + if action not in ('opened', 'reopened'): + print(f'Skipping event_action={action!r} โ€” only opened/reopened are forwarded.') + sys.exit(0) + + emoji = {'opened': '๐Ÿ†•', 'reopened': '๐Ÿ”„'}.get(action, 'โ„น๏ธ') + + repo = require_repo_name('REPO_NAME') + num = require_env('ISSUE_NUMBER') + title = sanitize_text(require_env('ISSUE_TITLE'), max_len=300) + url = require_env('ISSUE_URL') author = sanitize_text(require_env('ISSUE_AUTHOR'), max_len=80) - proj_gid = require_group_id('PROJECT_GROUP_ID') - - feed_msg = f"{emoji} [{repo}] Issue #{num} ยท {title}\n๐Ÿ‘ค {author}{labels_part}\n๐Ÿ”— {url}" - proj_msg = f"{emoji} Issue #{num} ยท {title}\n๐Ÿ‘ค {author}{labels_part}\n๐Ÿ”— {url}" - + + feed_msg = f"{emoji} [{repo}] Issue #{num} ยท {title}\n๐Ÿ‘ค {author}\n๐Ÿ”— {url}" + ALLOWED_API_BASES = { 'https://im.deepminer.com.cn/api', } @@ -125,13 +106,11 @@ jobs: api = _api_base + '/v1/bot/sendMessage' token = require_env('OCTO_BOT_TOKEN') headers = {'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'} - + failed = [] - - def send(group_id, message, mention_uids=None): + + def send(group_id, message): payload = {'type': 1, 'content': message} - if mention_uids: - payload['mention'] = {'uids': mention_uids} body = json.dumps({ 'channel_id': group_id, 'channel_type': 2, @@ -143,7 +122,7 @@ jobs: try: with urllib.request.urlopen(req, timeout=15) as r: print(f' โ†’ {group_id[:8]}... HTTP {r.status}') - return # success + return except urllib.error.HTTPError as e: last_err = e if e.code in (429, 500, 502, 503, 504) and attempt < 3: @@ -167,21 +146,12 @@ jobs: time.sleep(wait) else: break - print(f'ERROR: failed to send message to {group_id[:8]}...: {last_err}') + print(f'ERROR: failed to send to {group_id[:8]}...: {last_err}') failed.append(group_id) - - send('151a45970e1546afa9e947ac36a5c4e5', feed_msg) - send(proj_gid, proj_msg) - - # Trigger IssueTriage bot on newly opened issues - if action == 'opened': - # repo is already validated by require_repo_name; safe to interpolate into URL - triage_msg = ( - '@[27pmzxX8NAD78c9d01e_bot:Octo ๅŠฉ็†-IssueTriage] [TRIAGE] ' - f'https://github.com/Mininglamp-OSS/{repo}/issues/{num}' - ) - send('151a45970e1546afa9e947ac36a5c4e5', triage_msg, - mention_uids=['27pmzxX8NAD78c9d01e_bot']) + + # Send to issue-feed group only + ISSUE_FEED_GROUP = '151a45970e1546afa9e947ac36a5c4e5' + send(ISSUE_FEED_GROUP, feed_msg) if failed: sys.exit(1) From 43bc77d201948b03f2654db1fee37cecd48c42fc Mon Sep 17 00:00:00 2001 From: merlin Date: Tue, 19 May 2026 21:30:26 +0800 Subject: [PATCH 2/2] fix(issue-feed): keep deprecated inputs for backward compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add issue_labels and project_group_id back as optional no-ops. Callers on @v1 that still pass these inputs will not break; the reusable accepts but silently ignores the values. This removes the need to coordinate the v1 tag move with caller updates โ€” callers can be migrated individually (or via the 8 follow-up caller PRs that switch to @main and drop these inputs) without any breaking window. The inputs will be dropped in a later cleanup PR after all callers are updated. --- .github/workflows/octo-issue-feed.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/octo-issue-feed.yml b/.github/workflows/octo-issue-feed.yml index 91a01d5..4cbf211 100644 --- a/.github/workflows/octo-issue-feed.yml +++ b/.github/workflows/octo-issue-feed.yml @@ -28,7 +28,21 @@ on: type: string required: false default: 'https://im.deepminer.com.cn/api' - description: 'Octo IM API base URL. Only the production endpoint is allowlisted.' + description: 'Octo IM API base URL. Only the production endpoint is allowlisted. Any other value will cause the workflow to fail.' + # โ”€โ”€ Deprecated inputs kept for backward compatibility โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # Callers still passing these (using the old @v1 reusable) will not break; + # the values are accepted and silently ignored. Remove from callers first, + # then remove from this reusable in a follow-up PR. + issue_labels: + type: string + required: false + default: '[]' + description: 'DEPRECATED โ€” no longer used. Remove from callers before dropping here.' + project_group_id: + type: string + required: false + default: '' + description: 'DEPRECATED โ€” no longer used. Remove from callers before dropping here.' secrets: OCTO_BOT_TOKEN: required: true