name: Auto-triage scan output # career-ops runs locally. Some users point their own scheduled automation at # this repo by mistake and file their personal scan results / run reports as # issues here. Those are created via the API / `gh issue create`, which bypasses # the issue templates and `blank_issues_enabled: false` entirely — so only a # workflow can catch them. This one explains, labels, and closes them: it # protects users' personal job-search data and keeps the public tracker clean. # # Deliberately CONSERVATIVE — it acts only when BOTH are true: # (a) the issue did NOT come through a template (no Code of Conduct checkbox), and # (b) it carries an unmistakable automated-report marker (boilerplate the # scheduled-scan reports ship with — not merely the word "scan", which # legitimate scanner-provider features use). # It never locks the issue, so a real report can always be reopened. on: issues: types: [opened, reopened] permissions: issues: write jobs: triage: runs-on: ubuntu-latest # Never auto-close issues opened by the owner or org members. if: ${{ github.event.issue.author_association != 'OWNER' && github.event.issue.author_association != 'MEMBER' }} steps: - uses: actions/github-script@v9 with: script: | const issue = context.payload.issue; const body = issue.body || ''; const hay = `${issue.title || ''}\n${body}`; // Gate A — template-created issues carry the CoC checkbox. Real // contributions always come through a template, so never touch them. if (/Code of Conduct/i.test(body)) return; // Gate B — must carry an unmistakable automated-report marker. const markers = [ /Generated by career-ops automated routine/i, /Scheduled Portal Scan/i, /Scheduled Run Report/i, /Automated [Ss]can( (Result|Failed))?/i, /Auto-scan Results/i, /Daily AI Job Scan/i, /Morning (Briefing|Automation Run)/i, /Nightly Graphify/i, /This is an automated scan result/i, /onboarding (required|not complete)/i, /Gmail MCP token/i, /new offers added to pipeline/i, ]; if (!markers.some((re) => re.test(hay))) return; const { owner, repo } = context.repo; const issue_number = issue.number; await github.rest.issues.createComment({ owner, repo, issue_number, body: [ `Hey @${issue.user.login} — this looks like automated output from your own career-ops setup landing on the upstream repo. 👋`, ``, `career-ops runs **entirely locally**: your pipeline, scans, and reports live on your own machine (\`data/\`, \`reports/\`) — they never need to be filed here, and posting them upstream puts personal job-search data on a public tracker. Closing to keep your data private and the tracker tidy.`, ``, `If you have a scheduled run that opens issues, point it at your **own fork** or a private repo — not \`${owner}/${repo}\`. Real bugs are always welcome via the bug-report template. Thanks for building with career-ops! 🚀`, ].join('\n'), }); try { await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['scan-output'] }); } catch (e) { core.info(`label add skipped: ${e.message}`); } await github.rest.issues.update({ owner, repo, issue_number, state: 'closed', state_reason: 'not_planned' }); core.info(`Closed #${issue_number} as scan-output.`);