AI Bug Triage Checker (Paste Errors → Get Fix Hints)

April 11, 2026 NEW

Important: No tool can magically “AI-fix” your site without context or risk. But you can build an “AI-like” bug checker that automates the parts that waste time: collecting errors, grouping duplicates, extracting signals, and generating a fix-first checklist.

This tool: captures browser errors (and optional manual bug reports), stores them as JSON, then shows an “AI triage” view that labels likely causes (missing script, undefined variable, CORS, 404 asset, mixed content, etc.) and suggests what to check next.

What it does:

  • Auto-captures JS errors + unhandled promise rejections.
  • Lets users send a “bug note” from a tiny floating button.
  • Saves reports to /tools/bug-ai/_inbox/ (no DB).
  • Groups duplicates by signature + counts frequency.
  • Generates an “AI triage” checklist with probable causes + next steps.

Install:

  1. Create: /tools/bug-ai/
  2. Create writable folder: /tools/bug-ai/_inbox/
  3. Save the script below as: /tools/bug-ai/index.php
  4. Set your token at the top.
  5. Embed the snippet site-wide (it’s displayed in the admin page).
  6. View triage: /tools/bug-ai/?admin=TOKEN
<?php
declare(strict_types=1);

/**
 * AI Bug Triage Checker (No DB)
 * File: /tools/bug-ai/index.php
 *
 * POST collector:
 *   POST /tools/bug-ai/?catch=TOKEN  JSON payload
 *
 * Admin triage:
 *   /tools/bug-ai/?admin=TOKEN
 *
 * Keeps everything lightweight and deterministic (rule-based "AI-like" hints).
 */

header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: strict-origin-when-cross-origin');

$CFG = [
  'token'     => 'CHANGE_ME_TOKEN',
  'inbox_dir' => __DIR__ . '/_inbox',
  'max_bytes' => 180_000,

  // Optional IP lock (recommended)
  'allow_ips' => [
    // '123.123.123.123',
  ],
];

function h(string $s): string { return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); }
function ensure_dir(string $dir): bool { return is_dir($dir) || @mkdir($dir, 0755, true); }
function ip(): string { return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; }

function ok_ip(array $allow): bool {
  if (!$allow) return true;
  return in_array(ip(), $allow, true);
}

function base_url(): string {
  $https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
    || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && stripos((string)$_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0);
  $proto = $https ? 'https://' : 'http://';
  $host  = $_SERVER['HTTP_HOST'] ?? '';
  $path  = $_SERVER['SCRIPT_NAME'] ?? '';
  return $proto . $host . $path;
}

function json_out(array $a, int $code = 200): void {
  http_response_code($code);
  header('Content-Type: application/json; charset=utf-8');
  echo json_encode($a, JSON_UNESCAPED_SLASHES);
  exit;
}

function safe_body(int $max): string {
  $raw = (string)file_get_contents('php://input');
  if (strlen($raw) > $max) $raw = substr($raw, 0, $max);
  return $raw;
}

function signature(array $r): string {
  $msg = strtolower(trim((string)($r['message'] ?? '')));
  $src = strtolower(trim((string)($r['source'] ?? '')));
  $ln  = (int)($r['line'] ?? 0);
  $col = (int)($r['col'] ?? 0);
  return substr(sha1($msg.'|'.$src.'|'.$ln.'|'.$col), 0, 16);
}

function list_reports(string $dir): array {
  $files = glob($dir . '/*.json') ?: [];
  rsort($files);
  $out = [];
  foreach ($files as $f) {
    $raw = @file_get_contents($f);
    if ($raw === false) continue;
    $j = json_decode($raw, true);
    if (!is_array($j)) continue;
    $j['_file'] = basename($f);
    $out[] = $j;
  }
  return $out;
}

/**
 * "AI-like" triage: deterministic rules that classify common web bugs.
 * Returns: ['label'=>..., 'why'=>..., 'next'=>[...]]
 */
function triage_hint(array $r): array {
  $msg = strtolower((string)($r['message'] ?? ''));
  $src = strtolower((string)($r['source'] ?? ''));
  $stack = strtolower((string)($r['stack'] ?? ''));
  $page = (string)($r['page'] ?? '');

  // Helper
  $out = function(string $label, string $why, array $next): array {
    return ['label'=>$label,'why'=>$why,'next'=>$next];
  };

  // Script/load failures
  if (strpos($msg, 'failed to fetch') !== false || strpos($msg, 'networkerror') !== false) {
    return $out('Network / fetch failure', 'A request failed or was blocked (offline, CORS, adblock, server down).', [
      'Open DevTools → Network → retry and inspect the failing request.',
      'Check CORS headers if this calls another domain.',
      'Confirm endpoint returns 200 and isn’t blocked by WAF/adblock.',
    ]);
  }
  if (strpos($msg, 'script error') !== false && $src === '') {
    return $out('Cross-origin script error', 'Browser hid details (often from a 3rd-party script without CORS).', [
      'If it’s your script, serve with proper CORS + source maps.',
      'If 3rd-party, decide if you can ignore or replace the script.',
    ]);
  }
  if (strpos($msg, 'unexpected token') !== false || strpos($msg, 'syntaxerror') !== false) {
    return $out('Syntax error', 'JavaScript failed to parse (often missing bracket, bad JSON, or wrong script type).', [
      'Check the referenced file/line in DevTools.',
      'If it’s JSON, validate the payload (trailing commas are common).',
      'Confirm you are not serving HTML to a JS endpoint (403/404 pages).',
    ]);
  }

  // Undefined / null
  if (strpos($msg, 'is not defined') !== false) {
    return $out('Missing variable / script order', 'Code used a variable/function before it exists (or script failed to load).', [
      'Verify the script that defines it is loaded (Network tab).',
      'Check script order (dependency must load first).',
      'Guard usage with feature detection or DOMContentLoaded.',
    ]);
  }
  if (strpos($msg, 'cannot read properties of null') !== false || strpos($msg, 'cannot read property') !== false) {
    return $out('Null DOM reference', 'Code tried to access an element that wasn’t found (selector mismatch or timing).', [
      'Check the selector exists on that page.',
      'Run the code after DOMContentLoaded.',
      'Add null checks before accessing properties.',
    ]);
  }
  if (strpos($msg, 'cannot set properties of null') !== false) {
    return $out('Null target assignment', 'Code tried to set a value on a missing element.', [
      'Confirm the element exists where the script runs.',
      'Avoid running site-wide scripts on pages that don’t have those elements (or gate them).',
    ]);
  }

  // CORS / mixed content / blocked
  if (strpos($msg, 'cors') !== false || strpos($stack, 'cors') !== false) {
    return $out('CORS blocked', 'Browser blocked a cross-origin request due to missing/incorrect CORS headers.', [
      'Add Access-Control-Allow-Origin on the API (or proxy through your domain).',
      'Avoid calling third-party APIs directly from the browser if they don’t support CORS.',
    ]);
  }
  if (strpos($msg, 'mixed content') !== false) {
    return $out('Mixed content', 'HTTPS page tried to load HTTP resource, which gets blocked.', [
      'Update assets/endpoints to https://',
      'Use protocol-relative URLs only if you know what you’re doing.',
    ]);
  }

  // Fallback
  return $out('General JS error', 'A client-side error occurred. Use the signature + stack to locate the failing code path.', [
    'Open the page and reproduce with DevTools open.',
    'Search your codebase for the signature message.',
    'If this repeats across many pages, fix it first (highest count).',
  ]);
}

ensure_dir($CFG['inbox_dir']);

// -------- Collector --------
if (isset($_GET['catch'])) {
  if (!ok_ip((array)$CFG['allow_ips'])) json_out(['ok'=>false,'error'=>'IP blocked'], 403);

  $tok = (string)($_GET['catch'] ?? '');
  if ($tok === '' || !hash_equals((string)$CFG['token'], $tok)) json_out(['ok'=>false,'error'=>'Forbidden'], 403);

  $raw = safe_body((int)$CFG['max_bytes']);
  $j = json_decode($raw, true);
  if (!is_array($j)) json_out(['ok'=>false,'error'=>'Expected JSON'], 400);

  $r = [
    'type'    => (string)($j['type'] ?? 'js_error'),
    'page'    => (string)($j['page'] ?? ''),
    'message' => (string)($j['message'] ?? ''),
    'source'  => (string)($j['source'] ?? ''),
    'line'    => (int)($j['line'] ?? 0),
    'col'     => (int)($j['col'] ?? 0),
    'stack'   => (string)($j['stack'] ?? ''),
    'note'    => (string)($j['note'] ?? ''),
    'ua'      => $_SERVER['HTTP_USER_AGENT'] ?? '',
    'ip'      => ip(),
    'at'      => gmdate('c'),
  ];
  $r['sig'] = signature($r);

  $id = gmdate('Ymd_His') . '_' . bin2hex(random_bytes(6));
  $path = rtrim($CFG['inbox_dir'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $id . '.json';
  @file_put_contents($path, json_encode($r, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES), LOCK_EX);

  json_out(['ok'=>true,'id'=>$id,'sig'=>$r['sig']]);
}

// -------- Admin triage --------
if (!isset($_GET['admin'])) { http_response_code(404); echo "Not found."; exit; }
if (!ok_ip((array)$CFG['allow_ips'])) { http_response_code(403); echo "IP blocked."; exit; }

$tok = (string)($_GET['admin'] ?? '');
if ($tok === '' || !hash_equals((string)$CFG['token'], $tok)) { http_response_code(403); echo "Forbidden."; exit; }

$reports = list_reports((string)$CFG['inbox_dir']);
$groups = [];

foreach ($reports as $r) {
  $sig = (string)($r['sig'] ?? signature($r));
  if (!isset($groups[$sig])) $groups[$sig] = ['count'=>0,'latest'=>null,'hint'=>null,'items'=>[]];
  $groups[$sig]['count']++;
  if ($groups[$sig]['latest'] === null) {
    $groups[$sig]['latest'] = $r;
    $groups[$sig]['hint'] = triage_hint($r);
  }
  $groups[$sig]['items'][] = $r;
}

uasort($groups, function($a,$b){ return (int)$b['count'] <=> (int)$a['count']; });

$endpoint = base_url() . '?catch=' . rawurlencode((string)$CFG['token']);

?><!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>AI Bug Triage Checker</title>
<meta name="robots" content="noindex,nofollow" />
<style>
  :root{
    --bg:#0b0f16; --panel:#111a26; --panel2:#0c1420;
    --text:#e8eef8; --muted:rgba(232,238,248,.72);
    --line:rgba(255,255,255,.10); --accent:#2a8cff;
    --shadow:0 18px 55px rgba(0,0,0,.45); --r:16px;
    --ok:#7cff9a; --bad:#ff7c7c; --warn:#ffd37c;
  }
  *{box-sizing:border-box}
  body{margin:0;background:var(--bg);color:var(--text);font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif}
  .wrap{max-width:1180px;margin:0 auto;padding:18px}
  .card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:14px;margin:12px 0;box-shadow:var(--shadow)}
  .pill{display:inline-block;padding:4px 10px;border-radius:999px;background:rgba(42,140,255,.14);border:1px solid rgba(42,140,255,.25);font-weight:900;font-size:12px}
  .muted{color:var(--muted)}
  .mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}
  .badge{display:inline-block;padding:2px 8px;border-radius:999px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.05);font-weight:950;font-size:12px}
  .bad{color:var(--bad);font-weight:950}
  .warn{color:var(--warn);font-weight:950}
  table{width:100%;border-collapse:collapse}
  th,td{padding:10px;border-bottom:1px solid rgba(255,255,255,.08);vertical-align:top}
  th{text-align:left;font-size:12px;opacity:.75}
  pre{margin:8px 0 0;padding:12px;border-radius:14px;border:1px solid var(--line);background:var(--panel2);white-space:pre-wrap;overflow:auto}
  .hr{height:1px;background:var(--line);margin:12px 0}
</style>
</head>
<body>
<div class="wrap">

  <div class="card">
    <div class="pill">Admin</div>
    <h1 style="margin:10px 0 6px;font-size:22px">AI Bug Triage Checker</h1>
    <div class="muted">Collect errors → group duplicates → show “AI-like” hints and a fix-first list.</div>
    <div style="margin-top:10px" class="muted">
      Collector endpoint: <span class="mono"><?php echo h($endpoint); ?></span>
    </div>

    <div class="hr"></div>

    <b>Embed snippet (paste site-wide)</b>
    <pre class="mono"><?php echo h("<script>
(function(){
  var ENDPOINT = " . json_encode($endpoint) . ";

  function post(payload){
    try{
      fetch(ENDPOINT, {
        method: 'POST',
        headers: {'Content-Type':'application/json'},
        body: JSON.stringify(payload),
        credentials: 'omit'
      }).catch(function(){});
    }catch(e){}
  }

  window.addEventListener('error', function(e){
    post({
      type: 'js_error',
      page: location.href,
      message: (e && e.message) ? e.message : 'Script error',
      source: (e && e.filename) ? e.filename : '',
      line: (e && e.lineno) ? e.lineno : 0,
      col: (e && e.colno) ? e.colno : 0,
      stack: (e && e.error && e.error.stack) ? e.error.stack : ''
    });
  });

  window.addEventListener('unhandledrejection', function(e){
    var msg = '';
    try{ msg = (e && e.reason) ? (e.reason.message || String(e.reason)) : 'Unhandled rejection'; }catch(_){}
    post({
      type: 'promise_rejection',
      page: location.href,
      message: msg,
      stack: (e && e.reason && e.reason.stack) ? e.reason.stack : ''
    });
  });

  // optional manual reporter (call from a button)
  window.reportBug = function(note){
    post({ type:'manual_report', page: location.href, message:'User report', note:String(note||'') });
  };
})();
</script>"); ?></pre>
  </div>

  <div class="card">
    <b>Fix-first list</b>
    <div class="muted" style="margin-top:6px">Highest-count groups first = biggest impact.</div>

    <?php if (!$groups): ?>
      <div class="muted" style="margin-top:10px">No reports yet.</div>
    <?php else: ?>
      <table>
        <thead>
          <tr>
            <th>Count</th>
            <th>Signature</th>
            <th>Label</th>
            <th>Latest message</th>
            <th>Likely cause</th>
            <th>Next steps</th>
          </tr>
        </thead>
        <tbody>
          <?php foreach ($groups as $sig => $g):
            $r = $g['latest'] ?? [];
            $hint = $g['hint'] ?? ['label'=>'','why'=>'','next'=>[]];
          ?>
            <tr>
              <td class="bad"><?php echo (int)$g['count']; ?></td>
              <td class="mono"><?php echo h((string)$sig); ?></td>
              <td class="badge"><?php echo h((string)$hint['label']); ?></td>
              <td><?php echo h((string)($r['message'] ?? '')); ?><div class="muted mono" style="margin-top:6px"><?php echo h((string)($r['page'] ?? '')); ?></div></td>
              <td class="muted"><?php echo h((string)$hint['why']); ?></td>
              <td class="muted"><?php
                $ns = is_array($hint['next']) ? $hint['next'] : [];
                echo h(implode(" | ", array_slice($ns, 0, 3)));
              ?></td>
            </tr>
          <?php endforeach; ?>
        </tbody>
      </table>
    <?php endif; ?>
  </div>

</div>
</body>
</html>

Comments (0)

No comments yet — be the first.

← Back to all scripts