Remove Sentry telemetry system from CodeGraph
Eliminates anonymous error reporting functionality that was collecting stack traces and error context via Sentry. Removes all telemetry-related code, configuration options, and documentation references.
This commit is contained in:
@@ -78,7 +78,6 @@ src/
|
|||||||
│ ├── index.ts # MCPServer class
|
│ ├── index.ts # MCPServer class
|
||||||
│ ├── tools.ts # MCP tool definitions
|
│ ├── tools.ts # MCP tool definitions
|
||||||
│ └── transport.ts # Stdio transport
|
│ └── transport.ts # Stdio transport
|
||||||
├── sentry.ts # Error tracking/reporting
|
|
||||||
└── bin/codegraph.ts # CLI entry point
|
└── bin/codegraph.ts # CLI entry point
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,6 @@ The interactive installer will:
|
|||||||
- Prompt to install `codegraph` globally (needed for hooks & MCP server to work)
|
- Prompt to install `codegraph` globally (needed for hooks & MCP server to work)
|
||||||
- Configure the MCP server in `~/.claude.json`
|
- Configure the MCP server in `~/.claude.json`
|
||||||
- Set up auto-allow permissions for CodeGraph tools
|
- Set up auto-allow permissions for CodeGraph tools
|
||||||
- Ask about [anonymous error reporting](#-telemetry) (opt-out available)
|
|
||||||
- Add global instructions to `~/.claude/CLAUDE.md` (teaches Claude how to use CodeGraph)
|
- Add global instructions to `~/.claude/CLAUDE.md` (teaches Claude how to use CodeGraph)
|
||||||
- Install Claude Code hooks for automatic index syncing
|
- Install Claude Code hooks for automatic index syncing
|
||||||
- Optionally initialize your current project
|
- Optionally initialize your current project
|
||||||
@@ -311,11 +310,10 @@ The installer will:
|
|||||||
1. Prompt to install `codegraph` globally (needed for hooks & MCP server)
|
1. Prompt to install `codegraph` globally (needed for hooks & MCP server)
|
||||||
2. Ask for installation location (global `~/.claude` or local `./.claude`)
|
2. Ask for installation location (global `~/.claude` or local `./.claude`)
|
||||||
3. Optionally set up auto-allow permissions
|
3. Optionally set up auto-allow permissions
|
||||||
4. Ask about [anonymous error reporting](#-telemetry) (opt-out available)
|
4. Configure the MCP server in `claude.json`
|
||||||
5. Configure the MCP server in `claude.json`
|
5. Add global instructions to `~/.claude/CLAUDE.md` (teaches Claude how to use CodeGraph)
|
||||||
6. Add global instructions to `~/.claude/CLAUDE.md` (teaches Claude how to use CodeGraph)
|
6. Install Claude Code hooks for automatic index syncing
|
||||||
7. Install Claude Code hooks for automatic index syncing
|
7. For local installs: initialize and index the current project
|
||||||
8. For local installs: initialize and index the current project
|
|
||||||
|
|
||||||
### `codegraph init [path]`
|
### `codegraph init [path]`
|
||||||
|
|
||||||
@@ -711,27 +709,6 @@ Run `codegraph init` in your project directory first.
|
|||||||
- Check if the file's language is supported
|
- Check if the file's language is supported
|
||||||
- Verify the file isn't excluded by config patterns
|
- Verify the file isn't excluded by config patterns
|
||||||
|
|
||||||
## 📡 Telemetry
|
|
||||||
|
|
||||||
CodeGraph collects anonymous error reports via [Sentry](https://sentry.io) to help diagnose and fix bugs. This is **enabled by default** in production environments (disabled in development/test).
|
|
||||||
|
|
||||||
**What is collected:**
|
|
||||||
- Error type, message, and stack trace (includes local file paths in the trace)
|
|
||||||
- CodeGraph version and process name (CLI or MCP server)
|
|
||||||
|
|
||||||
**What is NOT collected:**
|
|
||||||
- Source code contents
|
|
||||||
- File contents or repository data
|
|
||||||
- Personal information or environment variables
|
|
||||||
|
|
||||||
**To opt out**, set the environment variable before running CodeGraph:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export CODEGRAPH_TELEMETRY=off
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📄 License
|
## 📄 License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import * as path from 'path';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import { getCodeGraphDir, findNearestCodeGraphRoot, isInitialized } from '../directory';
|
import { getCodeGraphDir, findNearestCodeGraphRoot, isInitialized } from '../directory';
|
||||||
import { initSentry, captureException } from '../sentry';
|
|
||||||
|
|
||||||
// Lazy-load heavy modules (CodeGraph, runInstaller) to keep CLI startup fast.
|
// Lazy-load heavy modules (CodeGraph, runInstaller) to keep CLI startup fast.
|
||||||
async function loadCodeGraph(): Promise<typeof import('../index')> {
|
async function loadCodeGraph(): Promise<typeof import('../index')> {
|
||||||
@@ -47,19 +46,10 @@ async function loadCodeGraph(): Promise<typeof import('../index')> {
|
|||||||
type IndexProgress = import('../index').IndexProgress;
|
type IndexProgress = import('../index').IndexProgress;
|
||||||
|
|
||||||
// Check if running with no arguments - run installer
|
// Check if running with no arguments - run installer
|
||||||
// Read version for Sentry release tag
|
|
||||||
const pkgVersion = (() => {
|
|
||||||
try {
|
|
||||||
return JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf-8')).version;
|
|
||||||
} catch { return undefined; }
|
|
||||||
})();
|
|
||||||
initSentry({ processName: 'codegraph-cli', version: pkgVersion });
|
|
||||||
|
|
||||||
if (process.argv.length === 2) {
|
if (process.argv.length === 2) {
|
||||||
import('../installer').then(({ runInstaller }) =>
|
import('../installer').then(({ runInstaller }) =>
|
||||||
runInstaller()
|
runInstaller()
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
captureException(err);
|
|
||||||
console.error('Installation failed:', err instanceof Error ? err.message : String(err));
|
console.error('Installation failed:', err instanceof Error ? err.message : String(err));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
@@ -69,12 +59,10 @@ if (process.argv.length === 2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
process.on('uncaughtException', (error) => {
|
process.on('uncaughtException', (error) => {
|
||||||
captureException(error);
|
|
||||||
console.error('[CodeGraph] Uncaught exception:', error);
|
console.error('[CodeGraph] Uncaught exception:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason) => {
|
process.on('unhandledRejection', (reason) => {
|
||||||
captureException(reason);
|
|
||||||
console.error('[CodeGraph] Unhandled rejection:', reason);
|
console.error('[CodeGraph] Unhandled rejection:', reason);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -296,7 +284,6 @@ program
|
|||||||
|
|
||||||
cg.destroy();
|
cg.destroy();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
captureException(err);
|
|
||||||
error(`Failed to initialize: ${err instanceof Error ? err.message : String(err)}`);
|
error(`Failed to initialize: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -342,7 +329,6 @@ program
|
|||||||
|
|
||||||
success(`Removed CodeGraph from ${projectPath}`);
|
success(`Removed CodeGraph from ${projectPath}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
captureException(err);
|
|
||||||
error(`Failed to uninitialize: ${err instanceof Error ? err.message : String(err)}`);
|
error(`Failed to uninitialize: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -411,7 +397,6 @@ program
|
|||||||
|
|
||||||
cg.destroy();
|
cg.destroy();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
captureException(err);
|
|
||||||
error(`Failed to index: ${err instanceof Error ? err.message : String(err)}`);
|
error(`Failed to index: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -469,7 +454,6 @@ program
|
|||||||
|
|
||||||
cg.destroy();
|
cg.destroy();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
captureException(err);
|
|
||||||
if (!options.quiet) {
|
if (!options.quiet) {
|
||||||
error(`Failed to sync: ${err instanceof Error ? err.message : String(err)}`);
|
error(`Failed to sync: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
}
|
}
|
||||||
@@ -581,7 +565,6 @@ program
|
|||||||
|
|
||||||
cg.destroy();
|
cg.destroy();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
captureException(err);
|
|
||||||
error(`Failed to get status: ${err instanceof Error ? err.message : String(err)}`);
|
error(`Failed to get status: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -644,7 +627,6 @@ program
|
|||||||
|
|
||||||
cg.destroy();
|
cg.destroy();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
captureException(err);
|
|
||||||
error(`Search failed: ${err instanceof Error ? err.message : String(err)}`);
|
error(`Search failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -770,7 +752,6 @@ program
|
|||||||
console.log();
|
console.log();
|
||||||
cg.destroy();
|
cg.destroy();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
captureException(err);
|
|
||||||
error(`Failed to list files: ${err instanceof Error ? err.message : String(err)}`);
|
error(`Failed to list files: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -898,7 +879,6 @@ program
|
|||||||
|
|
||||||
cg.destroy();
|
cg.destroy();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
captureException(err);
|
|
||||||
error(`Failed to build context: ${err instanceof Error ? err.message : String(err)}`);
|
error(`Failed to build context: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -949,7 +929,6 @@ program
|
|||||||
console.error(chalk.cyan(' codegraph_status') + ' - Get index status');
|
console.error(chalk.cyan(' codegraph_status') + ' - Get index status');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
captureException(err);
|
|
||||||
error(`Failed to start server: ${err instanceof Error ? err.message : String(err)}`);
|
error(`Failed to start server: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -1013,7 +992,6 @@ program
|
|||||||
process.on('SIGINT', shutdown);
|
process.on('SIGINT', shutdown);
|
||||||
process.on('SIGTERM', shutdown);
|
process.on('SIGTERM', shutdown);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
captureException(err);
|
|
||||||
error(`Failed to start visualizer: ${err instanceof Error ? err.message : String(err)}`);
|
error(`Failed to start visualizer: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -1126,7 +1104,6 @@ program
|
|||||||
fs.unlinkSync(lockPath);
|
fs.unlinkSync(lockPath);
|
||||||
success('Removed lock file. You can now run indexing again.');
|
success('Removed lock file. You can now run indexing again.');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
captureException(err);
|
|
||||||
error(`Failed to remove lock: ${err instanceof Error ? err.message : String(err)}`);
|
error(`Failed to remove lock: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -1265,7 +1242,6 @@ program
|
|||||||
|
|
||||||
cg.destroy();
|
cg.destroy();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
captureException(err);
|
|
||||||
error(`Affected analysis failed: ${err instanceof Error ? err.message : String(err)}`);
|
error(`Affected analysis failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,16 +240,6 @@ export class QueryBuilder {
|
|||||||
updatedAt: node.updatedAt ?? Date.now(),
|
updatedAt: node.updatedAt ?? Date.now(),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const { captureException } = require('../sentry');
|
|
||||||
captureException(error, {
|
|
||||||
operation: 'insertNode',
|
|
||||||
nodeId: node.id,
|
|
||||||
nodeKind: node.kind,
|
|
||||||
nodeName: node.name,
|
|
||||||
filePath: node.filePath,
|
|
||||||
language: node.language,
|
|
||||||
startLine: node.startLine,
|
|
||||||
});
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-6
@@ -233,13 +233,8 @@ export function logWarn(message: string, context?: Record<string, unknown>): voi
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log an error message (also sends to Sentry if initialized)
|
* Log an error message
|
||||||
*/
|
*/
|
||||||
export function logError(message: string, context?: Record<string, unknown>): void {
|
export function logError(message: string, context?: Record<string, unknown>): void {
|
||||||
currentLogger.error(message, context);
|
currentLogger.error(message, context);
|
||||||
// Lazy import to avoid circular deps
|
|
||||||
try {
|
|
||||||
const { captureMessage } = require('./sentry');
|
|
||||||
captureMessage(message, context);
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import { QueryBuilder } from '../db/queries';
|
|||||||
import { extractFromSource } from './tree-sitter';
|
import { extractFromSource } from './tree-sitter';
|
||||||
import { detectLanguage, isLanguageSupported, initGrammars, loadGrammarsForLanguages } from './grammars';
|
import { detectLanguage, isLanguageSupported, initGrammars, loadGrammarsForLanguages } from './grammars';
|
||||||
import { logDebug, logWarn } from '../errors';
|
import { logDebug, logWarn } from '../errors';
|
||||||
import { captureException } from '../sentry';
|
|
||||||
import { validatePathWithinRoot, normalizePath } from '../utils';
|
import { validatePathWithinRoot, normalizePath } from '../utils';
|
||||||
import picomatch from 'picomatch';
|
import picomatch from 'picomatch';
|
||||||
|
|
||||||
@@ -257,7 +256,6 @@ function scanDirectoryWalk(
|
|||||||
try {
|
try {
|
||||||
entries = fs.readdirSync(dir, { withFileTypes: true });
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureException(error, { operation: 'walk-directory', dir });
|
|
||||||
logDebug('Skipping unreadable directory', { dir, error: String(error) });
|
logDebug('Skipping unreadable directory', { dir, error: String(error) });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -548,7 +546,6 @@ export class ExtractionOrchestrator {
|
|||||||
stats = await fsp.stat(fullPath);
|
stats = await fsp.stat(fullPath);
|
||||||
content = await fsp.readFile(fullPath, 'utf-8');
|
content = await fsp.readFile(fullPath, 'utf-8');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureException(error, { operation: 'extract-file', filePath: fullPath });
|
|
||||||
return {
|
return {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
edges: [],
|
edges: [],
|
||||||
@@ -744,7 +741,6 @@ export class ExtractionOrchestrator {
|
|||||||
try {
|
try {
|
||||||
content = fs.readFileSync(fullPath, 'utf-8');
|
content = fs.readFileSync(fullPath, 'utf-8');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureException(error, { operation: 'sync-read-file', filePath });
|
|
||||||
logDebug('Skipping unreadable file during sync', { filePath, error: String(error) });
|
logDebug('Skipping unreadable file during sync', { filePath, error: String(error) });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -796,7 +792,6 @@ export class ExtractionOrchestrator {
|
|||||||
try {
|
try {
|
||||||
content = fs.readFileSync(fullPath, 'utf-8');
|
content = fs.readFileSync(fullPath, 'utf-8');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureException(error, { operation: 'sync-read-file', filePath });
|
|
||||||
logDebug('Skipping unreadable file during sync', { filePath, error: String(error) });
|
logDebug('Skipping unreadable file during sync', { filePath, error: String(error) });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -876,7 +871,6 @@ export class ExtractionOrchestrator {
|
|||||||
try {
|
try {
|
||||||
content = fs.readFileSync(fullPath, 'utf-8');
|
content = fs.readFileSync(fullPath, 'utf-8');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureException(error, { operation: 'detect-changes-read-file', filePath });
|
|
||||||
logDebug('Skipping unreadable file while detecting changes', { filePath, error: String(error) });
|
logDebug('Skipping unreadable file while detecting changes', { filePath, error: String(error) });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -927,7 +921,6 @@ export class ExtractionOrchestrator {
|
|||||||
try {
|
try {
|
||||||
content = fs.readFileSync(fullPath, 'utf-8');
|
content = fs.readFileSync(fullPath, 'utf-8');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureException(error, { operation: 'detect-changes-read-file', filePath });
|
|
||||||
logDebug('Skipping unreadable file while detecting changes', { filePath, error: String(error) });
|
logDebug('Skipping unreadable file while detecting changes', { filePath, error: String(error) });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
UnresolvedReference,
|
UnresolvedReference,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { getParser, detectLanguage, isLanguageSupported } from './grammars';
|
import { getParser, detectLanguage, isLanguageSupported } from './grammars';
|
||||||
import { captureException } from '../sentry';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a unique node ID
|
* Generate a unique node ID
|
||||||
@@ -961,7 +960,6 @@ export class TreeSitterExtractor {
|
|||||||
this.visitNode(this.tree.rootNode);
|
this.visitNode(this.tree.rootNode);
|
||||||
this.nodeStack.pop();
|
this.nodeStack.pop();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureException(error, { operation: 'tree-sitter-parse', filePath: this.filePath, language: this.language });
|
|
||||||
this.errors.push({
|
this.errors.push({
|
||||||
message: `Parse error: ${error instanceof Error ? error.message : String(error)}`,
|
message: `Parse error: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
@@ -2514,7 +2512,6 @@ export class LiquidExtractor {
|
|||||||
// Extract assign statements as variables
|
// Extract assign statements as variables
|
||||||
this.extractAssignments(fileNode.id);
|
this.extractAssignments(fileNode.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureException(error, { operation: 'liquid-extraction', filePath: this.filePath });
|
|
||||||
this.errors.push({
|
this.errors.push({
|
||||||
message: `Liquid extraction error: ${error instanceof Error ? error.message : String(error)}`,
|
message: `Liquid extraction error: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
@@ -2856,7 +2853,6 @@ export class SvelteExtractor {
|
|||||||
this.processScriptBlock(block, componentNode.id);
|
this.processScriptBlock(block, componentNode.id);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureException(error, { operation: 'svelte-extraction', filePath: this.filePath });
|
|
||||||
this.errors.push({
|
this.errors.push({
|
||||||
message: `Svelte extraction error: ${error instanceof Error ? error.message : String(error)}`,
|
message: `Svelte extraction error: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
@@ -3047,7 +3043,6 @@ export class DfmExtractor {
|
|||||||
const fileNode = this.createFileNode();
|
const fileNode = this.createFileNode();
|
||||||
this.parseComponents(fileNode.id);
|
this.parseComponents(fileNode.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureException(error, { operation: 'dfm-extraction', filePath: this.filePath });
|
|
||||||
this.errors.push({
|
this.errors.push({
|
||||||
message: `DFM extraction error: ${error instanceof Error ? error.message : String(error)}`,
|
message: `DFM extraction error: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
|
|||||||
@@ -100,22 +100,18 @@ function writeJsonFile(filePath: string, data: Record<string, any>): void {
|
|||||||
/**
|
/**
|
||||||
* Get the MCP server configuration
|
* Get the MCP server configuration
|
||||||
*/
|
*/
|
||||||
function getMcpServerConfig(options?: { telemetry?: boolean }): Record<string, any> {
|
function getMcpServerConfig(): Record<string, any> {
|
||||||
const config: Record<string, any> = {
|
return {
|
||||||
type: 'stdio',
|
type: 'stdio',
|
||||||
command: 'codegraph',
|
command: 'codegraph',
|
||||||
args: ['serve', '--mcp'],
|
args: ['serve', '--mcp'],
|
||||||
};
|
};
|
||||||
if (options?.telemetry === false) {
|
|
||||||
config.env = { CODEGRAPH_TELEMETRY: 'off' };
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write the MCP server configuration to claude.json
|
* Write the MCP server configuration to claude.json
|
||||||
*/
|
*/
|
||||||
export function writeMcpConfig(location: InstallLocation, options?: { telemetry?: boolean }): void {
|
export function writeMcpConfig(location: InstallLocation): void {
|
||||||
const claudeJsonPath = getClaudeJsonPath(location);
|
const claudeJsonPath = getClaudeJsonPath(location);
|
||||||
const config = readJsonFile(claudeJsonPath);
|
const config = readJsonFile(claudeJsonPath);
|
||||||
|
|
||||||
@@ -125,7 +121,7 @@ export function writeMcpConfig(location: InstallLocation, options?: { telemetry?
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add or update codegraph server
|
// Add or update codegraph server
|
||||||
config.mcpServers.codegraph = getMcpServerConfig(options);
|
config.mcpServers.codegraph = getMcpServerConfig();
|
||||||
|
|
||||||
writeJsonFile(claudeJsonPath, config);
|
writeJsonFile(claudeJsonPath, config);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-14
@@ -55,21 +55,9 @@ export async function runInstaller(): Promise<void> {
|
|||||||
const autoAllow = await promptAutoAllow();
|
const autoAllow = await promptAutoAllow();
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
// Step 4: Ask about anonymous error reporting
|
// Step 4: Write MCP configuration
|
||||||
console.log(chalk.bold(' Send anonymous error reports?') + chalk.dim(' (Helps fix bugs — no source code collected)'));
|
|
||||||
console.log();
|
|
||||||
const enableTelemetry = await promptConfirm('Enable anonymous error reporting', true);
|
|
||||||
|
|
||||||
if (!enableTelemetry) {
|
|
||||||
info('Telemetry disabled');
|
|
||||||
} else {
|
|
||||||
success('Anonymous error reporting enabled');
|
|
||||||
}
|
|
||||||
console.log();
|
|
||||||
|
|
||||||
// Step 5: Write MCP configuration (includes telemetry env if opted out)
|
|
||||||
const alreadyHasMcp = hasMcpConfig(location);
|
const alreadyHasMcp = hasMcpConfig(location);
|
||||||
writeMcpConfig(location, { telemetry: enableTelemetry });
|
writeMcpConfig(location);
|
||||||
|
|
||||||
if (alreadyHasMcp) {
|
if (alreadyHasMcp) {
|
||||||
success(`Updated MCP server in ${location === 'global' ? '~/.claude.json' : './.claude.json'}`);
|
success(`Updated MCP server in ${location === 'global' ? '~/.claude.json' : './.claude.json'}`);
|
||||||
|
|||||||
@@ -19,9 +19,6 @@ import * as path from 'path';
|
|||||||
import CodeGraph, { findNearestCodeGraphRoot } from '../index';
|
import CodeGraph, { findNearestCodeGraphRoot } from '../index';
|
||||||
import { StdioTransport, JsonRpcRequest, JsonRpcNotification, ErrorCodes } from './transport';
|
import { StdioTransport, JsonRpcRequest, JsonRpcNotification, ErrorCodes } from './transport';
|
||||||
import { tools, ToolHandler } from './tools';
|
import { tools, ToolHandler } from './tools';
|
||||||
import { initSentry, captureException } from '../sentry';
|
|
||||||
|
|
||||||
initSentry({ processName: 'codegraph-mcp' });
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a file:// URI to a filesystem path.
|
* Convert a file:// URI to a filesystem path.
|
||||||
@@ -121,7 +118,6 @@ export class MCPServer {
|
|||||||
this.toolHandler.setDefaultCodeGraph(this.cg);
|
this.toolHandler.setDefaultCodeGraph(this.cg);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Log the error so transient failures are diagnosable (see issue #47)
|
// Log the error so transient failures are diagnosable (see issue #47)
|
||||||
captureException(err);
|
|
||||||
const msg = err instanceof Error ? err.message : String(err);
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
process.stderr.write(`[CodeGraph MCP] Failed to open project at ${resolvedRoot}: ${msg}\n`);
|
process.stderr.write(`[CodeGraph MCP] Failed to open project at ${resolvedRoot}: ${msg}\n`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -370,7 +370,6 @@ export class ToolHandler {
|
|||||||
return this.errorResult(`Unknown tool: ${toolName}`);
|
return this.errorResult(`Unknown tool: ${toolName}`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
try { const { captureException } = require('../sentry'); captureException(err, { tool: toolName }); } catch {}
|
|
||||||
return this.errorResult(`Tool execution failed: ${err instanceof Error ? err.message : String(err)}`);
|
return this.errorResult(`Tool execution failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as readline from 'readline';
|
import * as readline from 'readline';
|
||||||
import { captureException } from '../sentry';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON-RPC 2.0 Request
|
* JSON-RPC 2.0 Request
|
||||||
@@ -163,7 +162,6 @@ export class StdioTransport {
|
|||||||
try {
|
try {
|
||||||
await this.messageHandler(parsed as JsonRpcRequest | JsonRpcNotification);
|
await this.messageHandler(parsed as JsonRpcRequest | JsonRpcNotification);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
captureException(err, { operation: 'mcp-message-handler' });
|
|
||||||
const message = parsed as JsonRpcRequest;
|
const message = parsed as JsonRpcRequest;
|
||||||
if ('id' in message) {
|
if ('id' in message) {
|
||||||
this.sendError(
|
this.sendError(
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { Node, UnresolvedReference, Edge } from '../types';
|
import { Node, UnresolvedReference, Edge } from '../types';
|
||||||
import { QueryBuilder } from '../db/queries';
|
import { QueryBuilder } from '../db/queries';
|
||||||
import { captureException } from '../sentry';
|
|
||||||
import {
|
import {
|
||||||
UnresolvedRef,
|
UnresolvedRef,
|
||||||
ResolvedRef,
|
ResolvedRef,
|
||||||
@@ -179,7 +178,6 @@ export class ReferenceResolver {
|
|||||||
try {
|
try {
|
||||||
return fs.existsSync(fullPath);
|
return fs.existsSync(fullPath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureException(error, { operation: 'resolution-file-exists', filePath });
|
|
||||||
logDebug('Error checking file existence', { filePath, error: String(error) });
|
logDebug('Error checking file existence', { filePath, error: String(error) });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -196,7 +194,6 @@ export class ReferenceResolver {
|
|||||||
this.fileCache.set(filePath, content);
|
this.fileCache.set(filePath, content);
|
||||||
return content;
|
return content;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureException(error, { operation: 'resolution-read-file', filePath });
|
|
||||||
logDebug('Failed to read file for resolution', { filePath, error: String(error) });
|
logDebug('Failed to read file for resolution', { filePath, error: String(error) });
|
||||||
this.fileCache.set(filePath, null);
|
this.fileCache.set(filePath, null);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
-174
@@ -1,174 +0,0 @@
|
|||||||
/**
|
|
||||||
* Lightweight Sentry client for CodeGraph — uses the HTTP envelope API directly.
|
|
||||||
* No @sentry/node dependency, works in any Node.js environment.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { createHash } from 'crypto';
|
|
||||||
|
|
||||||
const DSN = 'https://9591f8aca69bcf98e9feb31544200b47@o1181972.ingest.us.sentry.io/4510840133713920';
|
|
||||||
const DSN_PARTS = DSN.match(/^https:\/\/([^@]+)@([^/]+)\/(.+)$/);
|
|
||||||
const PUBLIC_KEY = DSN_PARTS![1];
|
|
||||||
const HOST = DSN_PARTS![2];
|
|
||||||
const PROJECT_ID = DSN_PARTS![3];
|
|
||||||
const STORE_URL = `https://${HOST}/api/${PROJECT_ID}/envelope/`;
|
|
||||||
|
|
||||||
let _enabled = false;
|
|
||||||
let _release = 'codegraph@unknown';
|
|
||||||
let _tags: Record<string, string> = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize Sentry error reporting.
|
|
||||||
* Safe to call multiple times — subsequent calls update tags/release.
|
|
||||||
*
|
|
||||||
* Opt out by setting the environment variable CODEGRAPH_TELEMETRY=off
|
|
||||||
*/
|
|
||||||
export function initSentry({ processName, version }: { processName: string; version?: string }) {
|
|
||||||
// Skip in development/test environments
|
|
||||||
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test' || process.env.VITEST) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Respect user opt-out
|
|
||||||
const telemetry = process.env.CODEGRAPH_TELEMETRY?.toLowerCase();
|
|
||||||
if (telemetry === 'off' || telemetry === '0' || telemetry === 'false') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_enabled = true;
|
|
||||||
_release = `codegraph@${version ?? process.env.npm_package_version ?? 'unknown'}`;
|
|
||||||
_tags = { processName };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an error to Sentry with full stack trace and context.
|
|
||||||
* Fire-and-forget — never throws, never blocks.
|
|
||||||
*/
|
|
||||||
export function captureException(error: unknown, extra?: Record<string, unknown>) {
|
|
||||||
if (!_enabled) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const err = error instanceof Error ? error : new Error(String(error));
|
|
||||||
const msg = err.message.toLowerCase();
|
|
||||||
|
|
||||||
// Filter non-actionable noise
|
|
||||||
if (msg.includes('pty') || msg.includes('terminal session')) return;
|
|
||||||
if ((msg.includes('econnrefused') || msg.includes('econnreset')) && msg.includes('127.0.0.1')) return;
|
|
||||||
|
|
||||||
const eventId = createHash('md5').update(`${Date.now()}-${Math.random()}`).digest('hex');
|
|
||||||
const timestamp = new Date().toISOString();
|
|
||||||
|
|
||||||
// Attach CodeGraphError context if available
|
|
||||||
const errorContext: Record<string, unknown> = { ...extra };
|
|
||||||
if ('code' in err && typeof (err as any).code === 'string') {
|
|
||||||
errorContext.errorCode = (err as any).code;
|
|
||||||
}
|
|
||||||
if ('context' in err && typeof (err as any).context === 'object') {
|
|
||||||
Object.assign(errorContext, (err as any).context);
|
|
||||||
}
|
|
||||||
|
|
||||||
const event: Record<string, unknown> = {
|
|
||||||
event_id: eventId,
|
|
||||||
timestamp,
|
|
||||||
platform: 'node',
|
|
||||||
level: 'error',
|
|
||||||
release: _release,
|
|
||||||
tags: _tags,
|
|
||||||
exception: {
|
|
||||||
values: [{
|
|
||||||
type: err.name,
|
|
||||||
value: err.message,
|
|
||||||
stacktrace: parseStack(err.stack),
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Object.keys(errorContext).length > 0) {
|
|
||||||
event.extra = errorContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = JSON.stringify(event);
|
|
||||||
const envelope = [
|
|
||||||
JSON.stringify({ event_id: eventId, sent_at: timestamp, dsn: DSN }),
|
|
||||||
JSON.stringify({ type: 'event', length: payload.length }),
|
|
||||||
payload,
|
|
||||||
].join('\n') + '\n';
|
|
||||||
|
|
||||||
fetch(STORE_URL, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-sentry-envelope',
|
|
||||||
'X-Sentry-Auth': `Sentry sentry_version=7, sentry_key=${PUBLIC_KEY}`,
|
|
||||||
},
|
|
||||||
body: envelope,
|
|
||||||
}).catch(() => {});
|
|
||||||
} catch {
|
|
||||||
// Never throw from error reporting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a message-level event to Sentry (for logged errors without Error objects).
|
|
||||||
*/
|
|
||||||
export function captureMessage(message: string, context?: Record<string, unknown>) {
|
|
||||||
if (!_enabled) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const eventId = createHash('md5').update(`${Date.now()}-${Math.random()}`).digest('hex');
|
|
||||||
const timestamp = new Date().toISOString();
|
|
||||||
|
|
||||||
const event: Record<string, unknown> = {
|
|
||||||
event_id: eventId,
|
|
||||||
timestamp,
|
|
||||||
platform: 'node',
|
|
||||||
level: 'error',
|
|
||||||
release: _release,
|
|
||||||
tags: _tags,
|
|
||||||
message: { formatted: message },
|
|
||||||
};
|
|
||||||
|
|
||||||
if (context && Object.keys(context).length > 0) {
|
|
||||||
event.extra = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = JSON.stringify(event);
|
|
||||||
const envelope = [
|
|
||||||
JSON.stringify({ event_id: eventId, sent_at: timestamp, dsn: DSN }),
|
|
||||||
JSON.stringify({ type: 'event', length: payload.length }),
|
|
||||||
payload,
|
|
||||||
].join('\n') + '\n';
|
|
||||||
|
|
||||||
fetch(STORE_URL, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-sentry-envelope',
|
|
||||||
'X-Sentry-Auth': `Sentry sentry_version=7, sentry_key=${PUBLIC_KEY}`,
|
|
||||||
},
|
|
||||||
body: envelope,
|
|
||||||
}).catch(() => {});
|
|
||||||
} catch {
|
|
||||||
// Never throw from error reporting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a Node.js Error.stack string into Sentry's stacktrace format.
|
|
||||||
*/
|
|
||||||
function parseStack(stack?: string): { frames: Array<{ filename: string; function: string; lineno?: number; colno?: number }> } | undefined {
|
|
||||||
if (!stack) return undefined;
|
|
||||||
|
|
||||||
const frames = stack
|
|
||||||
.split('\n')
|
|
||||||
.slice(1)
|
|
||||||
.map((line) => {
|
|
||||||
const match = line.match(/^\s+at\s+(?:(.+?)\s+\()?(.*?):(\d+):(\d+)\)?$/);
|
|
||||||
if (!match || !match[2] || !match[3] || !match[4]) return null;
|
|
||||||
return {
|
|
||||||
function: match[1] || '<anonymous>',
|
|
||||||
filename: match[2],
|
|
||||||
lineno: parseInt(match[3], 10),
|
|
||||||
colno: parseInt(match[4], 10),
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter((f): f is NonNullable<typeof f> => f !== null)
|
|
||||||
.reverse();
|
|
||||||
|
|
||||||
return frames.length > 0 ? { frames } : undefined;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user