delete the bug-report discord webhook, and the last dead HOME_DIR reads
DISCORD_BUG_REPORT_WEBHOOK is gone, with sendToDiscord and its helpers. Reports still land in DATA_PATH/bug-reports — the disk write always happened first and the webhook was only a ping about it, so nothing about the report is lost. It was a personal notification channel living in deployment config, on a platform whose owner is the only person who files reports. It was also never in .env.example: the setup script wrote a variable nothing documented, which is the same drift as PORT, in the other direction. Note DISCORD_WEBHOOK_URL is a DIFFERENT variable — the notify sidecar's own channel — and is untouched. Then a parity sweep of setup / .env.example / what the code reads, which turned up two leftovers from earlier today: HOME_DIR was still read in six files, each with its own `?? homedir()` fallback. Dead since nothing sets it, but a dead read is worse than none — it reads as a supported override. They take homedir() directly now. user-instance.ts gets a comment on why its line stays where it is: it sits above `process.env.HOME = homeDir`, and homedir() reads $HOME, so a read moved below that assignment would return whichever member was last spawned into. Two of the six had fallback chains ending in process.cwd() and '' — the second would have silently disabled whatever consumed it rather than failing. VAULTWARDEN_URL was uncommented in .env.example among the variables setup writes, though it is a plugin variable setup has never written. Commented out with the other plugin entries. The three files now agree: setup writes PORT, BROWSER_RELAY_PORT and POSTGRES_URL; .env.example lists those plus JWT_SECRET and VAULT_STORE_KEY, which are required by code and deliberately unwritten until the secret store lands. Not typechecked (empty node_modules, frozen installs). Every changed file parses; the setup section was run and writes three variables. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,8 +13,9 @@ import { parseTailLine } from './progress';
|
||||
export const activityRouter = createRouter();
|
||||
|
||||
import { DATA_PATH } from '../../data-path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const HOME_DIR = process.env.HOME_DIR ?? process.env.HOME ?? '';
|
||||
const HOME_DIR = homedir();
|
||||
const ANNOUNCED_PATH = join(DATA_PATH, 'activity', 'announced.json');
|
||||
const ALLOWED_ROOTS = ['/tmp', DATA_PATH, HOME_DIR].filter(Boolean);
|
||||
const ACTIVE_WINDOW_MS = 120_000; // a task file touched within this is considered "active"
|
||||
|
||||
@@ -3,7 +3,15 @@ import { mkdir } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { DATA_PATH } from '@@/data-path';
|
||||
|
||||
const DISCORD_WEBHOOK_URL = process.env.DISCORD_BUG_REPORT_WEBHOOK;
|
||||
// Bug reports land on disk and nowhere else.
|
||||
//
|
||||
// There was a Discord webhook here until 2026-08-13, behind DISCORD_BUG_REPORT_WEBHOOK. It was a
|
||||
// personal notification channel living in deployment config, on a self-hosted platform whose owner is
|
||||
// the only person filing reports — and it was never in .env.example, so the setup script wrote a
|
||||
// variable nothing documented.
|
||||
//
|
||||
// Nothing is lost from the report itself: the disk write below always happened first, and the webhook
|
||||
// was only a ping about it.
|
||||
|
||||
export const bugReportRouter = createRouter();
|
||||
|
||||
@@ -36,73 +44,9 @@ bugReportRouter.post('/', async (ctx) => {
|
||||
|
||||
await Bun.write(join(reportDir, 'report.json'), JSON.stringify(report, null, 2));
|
||||
|
||||
let screenshotBuffer: Buffer | null = null;
|
||||
if (screenshot instanceof File) {
|
||||
screenshotBuffer = Buffer.from(await screenshot.arrayBuffer());
|
||||
await Bun.write(join(reportDir, 'screenshot.png'), screenshotBuffer);
|
||||
}
|
||||
|
||||
if (DISCORD_WEBHOOK_URL) {
|
||||
await sendToDiscord(report, screenshotBuffer);
|
||||
await Bun.write(join(reportDir, 'screenshot.png'), Buffer.from(await screenshot.arrayBuffer()));
|
||||
}
|
||||
|
||||
return ctx.json({ ok: true, id: dirName });
|
||||
});
|
||||
|
||||
type BugReport = {
|
||||
description: string;
|
||||
context: {
|
||||
url?: string;
|
||||
userAgent?: string;
|
||||
viewport?: { width: number; height: number };
|
||||
apiError?: { status: number; message: string } | null;
|
||||
} | null;
|
||||
reporter: { id: number; email: string; name: string | null };
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
async function sendToDiscord(report: BugReport, screenshot: Buffer | null) {
|
||||
const embed = {
|
||||
title: 'Bug Report',
|
||||
description: report.description,
|
||||
color: 0xed4245,
|
||||
fields: [
|
||||
{ name: 'Reporter', value: `${report.reporter.name} (${report.reporter.email})`, inline: true },
|
||||
{ name: 'URL', value: report.context?.url ?? 'N/A', inline: false },
|
||||
{
|
||||
name: 'Viewport',
|
||||
value: report.context?.viewport ? `${report.context.viewport.width}x${report.context.viewport.height}` : 'N/A',
|
||||
inline: true,
|
||||
},
|
||||
{ name: 'Browser', value: shortenUA(report.context?.userAgent), inline: true },
|
||||
],
|
||||
timestamp: report.createdAt,
|
||||
};
|
||||
|
||||
if (report.context?.apiError) {
|
||||
embed.fields.push({
|
||||
name: 'Last API Error',
|
||||
value: `${report.context.apiError.status}: ${report.context.apiError.message}`,
|
||||
inline: false,
|
||||
});
|
||||
}
|
||||
|
||||
const form = new FormData();
|
||||
form.append('payload_json', JSON.stringify({ embeds: [embed] }));
|
||||
|
||||
if (screenshot) {
|
||||
form.append('files[0]', new Blob([new Uint8Array(screenshot)], { type: 'image/png' }), 'screenshot.png');
|
||||
}
|
||||
|
||||
const res = await fetch(DISCORD_WEBHOOK_URL!, { method: 'POST', body: form });
|
||||
if (!res.ok) {
|
||||
console.error('[bug-report] Discord webhook failed:', res.status, await res.text());
|
||||
}
|
||||
}
|
||||
|
||||
function shortenUA(ua?: string): string {
|
||||
if (!ua) return 'N/A';
|
||||
const browser = ua.match(/(Chrome|Firefox|Safari|Edge|Brave|OPR)\/[\d.]+/)?.[0] ?? '';
|
||||
const os = ua.match(/\(([^)]+)\)/)?.[1]?.split(';')[0] ?? '';
|
||||
return [browser, os].filter(Boolean).join(' — ') || ua.slice(0, 80);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ import { DATA_PATH } from '../../data-path';
|
||||
//
|
||||
// ── Why this takes a home instead of an email ──
|
||||
//
|
||||
// It used to be `process.env.HOME_DIR ?? join(DATA_PATH, email, 'home')`, which discards its argument whenever
|
||||
// HOME_DIR is set — which is always, on a real install. Every read therefore resolved to the OWNER'S
|
||||
// It used to be `process.env.HOME_DIR ?? join(DATA_PATH, email, 'home')`, which discarded its argument
|
||||
// whenever HOME_DIR was set — which was always, on a real install. Every read therefore resolved to the OWNER'S
|
||||
// transcripts regardless of who was asking, and the comment above it said "single-user platform" as though
|
||||
// that were a property rather than an assumption. A member reaching these functions would have been handed the
|
||||
// owner's conversation list.
|
||||
|
||||
Reference in New Issue
Block a user