Skip to content

Instantly share code, notes, and snippets.

@Trippnology
Created October 20, 2025 12:56
Show Gist options
  • Select an option

  • Save Trippnology/e1b0deb1c4b0dceaee92003ead1a339a to your computer and use it in GitHub Desktop.

Select an option

Save Trippnology/e1b0deb1c4b0dceaee92003ead1a339a to your computer and use it in GitHub Desktop.
Clear GitHub spam notifications - Mark GitHub notifications as read
#!/usr/bin/env node
/**
* GitHub Notification Cleaner
* Clears all notifications including unreachable ones from deleted accounts
*
* Setup:
* 1. Create a Personal Access Token at https://github.com/settings/tokens
* - Required scope: notifications
* 2. Set the token as an environment variable: GITHUB_TOKEN=your_token_here
* 3. Run: node clear-notifications.js
*/
const https = require('node:https');
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
const API_BASE = 'api.github.com';
if (!GITHUB_TOKEN) {
console.error('Error: GITHUB_TOKEN environment variable not set');
console.error('Create a token at: https://github.com/settings/tokens');
console.error('Required scope: notifications');
process.exit(1);
}
function makeRequest(path, method = 'GET', data = null) {
return new Promise((resolve, reject) => {
const options = {
hostname: API_BASE,
path: path,
method: method,
headers: {
'User-Agent': 'GitHub-Notification-Cleaner',
Authorization: `Bearer ${GITHUB_TOKEN}`,
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
},
};
if (data) {
const jsonData = JSON.stringify(data);
options.headers['Content-Type'] = 'application/json';
options.headers['Content-Length'] = Buffer.byteLength(jsonData);
}
const req = https.request(options, (res) => {
let body = '';
res.on('data', (chunk) => {
body += chunk;
});
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve({
statusCode: res.statusCode,
data: body ? JSON.parse(body) : null,
});
} else {
reject(new Error(`HTTP ${res.statusCode}: ${body}`));
}
});
});
req.on('error', reject);
if (data) {
req.write(JSON.stringify(data));
}
req.end();
});
}
async function getNotifications() {
console.log('Fetching notifications...');
const response = await makeRequest('/notifications?all=true&per_page=100');
return response.data;
}
async function markNotificationAsRead(threadId) {
await makeRequest(`/notifications/threads/${threadId}`, 'PATCH');
}
async function markAllAsRead() {
const lastReadAt = new Date().toISOString();
await makeRequest('/notifications', 'PUT', { last_read_at: lastReadAt });
}
async function clearAllNotifications() {
try {
console.log('Starting notification cleanup...\n');
// Get all notifications
const notifications = await getNotifications();
if (notifications.length === 0) {
console.log('✓ No notifications found. Your inbox is clean!');
return;
}
console.log(`Found ${notifications.length} notification(s)\n`);
// Try to mark each individually first (catches unreachable ones)
let cleared = 0;
let failed = [];
for (const notif of notifications) {
try {
await markNotificationAsRead(notif.id);
cleared++;
console.log(
`✓ Cleared: ${notif.subject.title.substring(0, 60)}...`,
);
} catch (err) {
failed.push(notif);
console.log(
`✗ Failed: ${notif.subject.title.substring(0, 60)}... (${err.message})`,
);
}
}
// Mark all as read to catch any remaining
console.log('\nMarking all notifications as read...');
await markAllAsRead();
console.log('\n' + '='.repeat(50));
console.log(`✓ Successfully cleared: ${cleared}`);
if (failed.length > 0) {
console.log(`✗ Failed to clear: ${failed.length}`);
console.log(' (These should be caught by "mark all as read")');
}
console.log('✓ All notifications marked as read');
console.log('='.repeat(50));
} catch (err) {
console.error('\nError:', err.message);
process.exit(1);
}
}
// Run the script
clearAllNotifications();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment