Remove the remaining no-op tracing and telemetry-only helpers

The build no longer ships telemetry egress, so the next cleanup pass deletes the remaining tracing compatibility layer and the helper modules whose only job was to shape telemetry payloads. This removes the dead session/beta/perfetto tracing files, drops telemetry-only file-operation and plugin-fetch helpers, and rewires the affected callers to keep only their real product behavior.

Constraint: Preserve existing user-visible behavior and feature-gated product logic while removing inert tracing/reporting scaffolding
Constraint: Leave GrowthBook in place for now because it functions as the repo's local feature-flag adapter, not a live reporting path
Rejected: Delete growthbook.ts in the same pass | Its call surface is wide and now tied to local product behavior rather than telemetry export
Rejected: Leave no-op tracing and helper modules in place | They continued to create audit noise and implied behavior that no longer existed
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Remaining analytics-named code should be treated as either local compatibility calls or feature-gate infrastructure unless a concrete egress path is reintroduced
Tested: bun test src/services/analytics/index.test.ts src/components/FeedbackSurvey/submitTranscriptShare.test.ts
Tested: bun run ./scripts/build.ts
Not-tested: bun x tsc --noEmit (repository has pre-existing unrelated type errors)
This commit is contained in:
2026-04-09 14:26:11 +08:00
parent 5af8acb2bb
commit 9ba783f10b
22 changed files with 11 additions and 1072 deletions

View File

@@ -1,135 +0,0 @@
/**
* Telemetry for plugin/marketplace fetches that hit the network.
*
* Added for inc-5046 (GitHub complained about claude-plugins-official load).
* Before this, fetch operations only had logForDebugging — no way to measure
* actual network volume. This surfaces what's hitting GitHub vs GCS vs
* user-hosted so we can see the GCS migration take effect and catch future
* hot-path regressions before GitHub emails us again.
*
* Volume: these fire at startup (install-counts 24h-TTL)
* and on explicit user action (install/update). NOT per-interaction. Similar
* envelope to tengu_binary_download_*.
*/
import {
logEvent,
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS as SafeString,
} from '../../services/analytics/index.js'
import { OFFICIAL_MARKETPLACE_NAME } from './officialMarketplace.js'
export type PluginFetchSource =
| 'install_counts'
| 'marketplace_clone'
| 'marketplace_pull'
| 'marketplace_url'
| 'plugin_clone'
| 'mcpb'
export type PluginFetchOutcome = 'success' | 'failure' | 'cache_hit'
// Allowlist of public hosts we report by name. Anything else (enterprise
// git, self-hosted, internal) is bucketed as 'other' — we don't want
// internal hostnames (git.mycorp.internal) landing in telemetry. Bounded
// cardinality also keeps the dashboard host-breakdown tractable.
const KNOWN_PUBLIC_HOSTS = new Set([
'github.com',
'raw.githubusercontent.com',
'objects.githubusercontent.com',
'gist.githubusercontent.com',
'gitlab.com',
'bitbucket.org',
'codeberg.org',
'dev.azure.com',
'ssh.dev.azure.com',
'storage.googleapis.com', // GCS — where Dickson's migration points
])
/**
* Extract hostname from a URL or git spec and bucket to the allowlist.
* Handles `https://host/...`, `git@host:path`, `ssh://host/...`.
* Returns a known public host, 'other' (parseable but not allowlisted —
* don't leak private hostnames), or 'unknown' (unparseable / local path).
*/
function extractHost(urlOrSpec: string): string {
let host: string
const scpMatch = /^[^@/]+@([^:/]+):/.exec(urlOrSpec)
if (scpMatch) {
host = scpMatch[1]!
} else {
try {
host = new URL(urlOrSpec).hostname
} catch {
return 'unknown'
}
}
const normalized = host.toLowerCase()
return KNOWN_PUBLIC_HOSTS.has(normalized) ? normalized : 'other'
}
/**
* True if the URL/spec points at anthropics/claude-plugins-official — the
* repo GitHub complained about. Lets the dashboard separate "our problem"
* traffic from user-configured marketplaces.
*/
function isOfficialRepo(urlOrSpec: string): boolean {
return urlOrSpec.includes(`anthropics/${OFFICIAL_MARKETPLACE_NAME}`)
}
export function logPluginFetch(
source: PluginFetchSource,
urlOrSpec: string | undefined,
outcome: PluginFetchOutcome,
durationMs: number,
errorKind?: string,
): void {
// String values are bounded enums / hostname-only — no code, no paths,
// no raw error messages. Same privacy envelope as tengu_web_fetch_host.
logEvent('tengu_plugin_remote_fetch', {
source: source as SafeString,
host: (urlOrSpec ? extractHost(urlOrSpec) : 'unknown') as SafeString,
is_official: urlOrSpec ? isOfficialRepo(urlOrSpec) : false,
outcome: outcome as SafeString,
duration_ms: Math.round(durationMs),
...(errorKind && { error_kind: errorKind as SafeString }),
})
}
/**
* Classify an error into a stable bucket for the error_kind field. Keeps
* cardinality bounded — raw error messages would explode dashboard grouping.
*
* Handles both axios Error objects (Node.js error codes like ENOTFOUND) and
* git stderr strings (human phrases like "Could not resolve host"). DNS
* checked BEFORE timeout because gitClone's error enhancement at
* marketplaceManager.ts:~950 rewrites DNS failures to include the word
* "timeout" — ordering the other way would misclassify git DNS as timeout.
*/
export function classifyFetchError(error: unknown): string {
const msg = String((error as { message?: unknown })?.message ?? error)
if (
/ENOTFOUND|ECONNREFUSED|EAI_AGAIN|Could not resolve host|Connection refused/i.test(
msg,
)
) {
return 'dns_or_refused'
}
if (/ETIMEDOUT|timed out|timeout/i.test(msg)) return 'timeout'
if (
/ECONNRESET|socket hang up|Connection reset by peer|remote end hung up/i.test(
msg,
)
) {
return 'conn_reset'
}
if (/403|401|authentication|permission denied/i.test(msg)) return 'auth'
if (/404|not found|repository not found/i.test(msg)) return 'not_found'
if (/certificate|SSL|TLS|unable to get local issuer/i.test(msg)) return 'tls'
// Schema validation throws "Invalid response format" (install_counts) —
// distinguish from true unknowns so the dashboard can
// see "server sent garbage" separately.
if (/Invalid response format|Invalid marketplace schema/i.test(msg)) {
return 'invalid_schema'
}
return 'other'
}

View File

@@ -17,7 +17,6 @@ import { errorMessage, getErrnoCode } from '../errors.js'
import { getFsImplementation } from '../fsOperations.js'
import { logError } from '../log.js'
import { jsonParse, jsonStringify } from '../slowOperations.js'
import { classifyFetchError, logPluginFetch } from './fetchTelemetry.js'
import { getPluginsDirectory } from './pluginDirectories.js'
const INSTALL_COUNTS_CACHE_VERSION = 1
@@ -196,21 +195,8 @@ async function fetchInstallCountsFromGitHub(): Promise<
throw new Error('Invalid response format from install counts API')
}
logPluginFetch(
'install_counts',
INSTALL_COUNTS_URL,
'success',
performance.now() - started,
)
return response.data.plugins
} catch (error) {
logPluginFetch(
'install_counts',
INSTALL_COUNTS_URL,
'failure',
performance.now() - started,
classifyFetchError(error),
)
throw error
}
}
@@ -227,7 +213,6 @@ export async function getInstallCounts(): Promise<Map<string, number> | null> {
const cache = await loadInstallCountsCache()
if (cache) {
logForDebugging('Using cached install counts')
logPluginFetch('install_counts', INSTALL_COUNTS_URL, 'cache_hit', 0)
const map = new Map<string, number>()
for (const entry of cache.counts) {
map.set(entry.plugin, entry.unique_installs)

View File

@@ -53,7 +53,6 @@ import {
getAddDirExtraMarketplaces,
} from './addDirPluginSettings.js'
import { markPluginVersionOrphaned } from './cacheUtils.js'
import { classifyFetchError, logPluginFetch } from './fetchTelemetry.js'
import { removeAllPluginsForMarketplace } from './installedPluginsManager.js'
import {
extractHostFromSource,
@@ -1110,13 +1109,7 @@ async function cacheMarketplaceFromGit(
disableCredentialHelper: options?.disableCredentialHelper,
sparsePaths,
})
logPluginFetch(
'marketplace_pull',
gitUrl,
pullResult.code === 0 ? 'success' : 'failure',
performance.now() - pullStarted,
pullResult.code === 0 ? undefined : classifyFetchError(pullResult.stderr),
)
void pullStarted
if (pullResult.code === 0) return
logForDebugging(`git pull failed, will re-clone: ${pullResult.stderr}`, {
level: 'warn',
@@ -1156,13 +1149,7 @@ async function cacheMarketplaceFromGit(
)
const cloneStarted = performance.now()
const result = await gitClone(gitUrl, cachePath, ref, sparsePaths)
logPluginFetch(
'marketplace_clone',
gitUrl,
result.code === 0 ? 'success' : 'failure',
performance.now() - cloneStarted,
result.code === 0 ? undefined : classifyFetchError(result.stderr),
)
void cloneStarted
if (result.code !== 0) {
// Clean up any partial directory created by the failed clone so the next
// attempt starts fresh. Best-effort: if this fails, the stale dir will be
@@ -1284,13 +1271,6 @@ async function cacheMarketplaceFromUrl(
headers,
})
} catch (error) {
logPluginFetch(
'marketplace_url',
url,
'failure',
performance.now() - fetchStarted,
classifyFetchError(error),
)
if (axios.isAxiosError(error)) {
if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
throw new Error(
@@ -1317,25 +1297,13 @@ async function cacheMarketplaceFromUrl(
// Validate the response is a valid marketplace
const result = PluginMarketplaceSchema().safeParse(response.data)
if (!result.success) {
logPluginFetch(
'marketplace_url',
url,
'failure',
performance.now() - fetchStarted,
'invalid_schema',
)
throw new ConfigParseError(
`Invalid marketplace schema from URL: ${result.error.issues.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
redactedUrl,
response.data,
)
}
logPluginFetch(
'marketplace_url',
url,
'success',
performance.now() - fetchStarted,
)
void fetchStarted
safeCallProgress(onProgress, 'Saving marketplace to cache')
// Ensure cache directory exists

View File

@@ -20,7 +20,6 @@ import {
} from '../settings/settings.js'
import { jsonParse, jsonStringify } from '../slowOperations.js'
import { getSystemDirectories } from '../systemDirectories.js'
import { classifyFetchError, logPluginFetch } from './fetchTelemetry.js'
/**
* User configuration values for MCPB
*/
@@ -490,7 +489,6 @@ async function downloadMcpb(
}
const started = performance.now()
let fetchTelemetryFired = false
try {
const response = await axios.get(url, {
timeout: 120000, // 2 minute timeout
@@ -507,11 +505,6 @@ async function downloadMcpb(
})
const data = new Uint8Array(response.data)
// Fire telemetry before writeFile — the event measures the network
// fetch, not disk I/O. A writeFile EACCES would otherwise match
// classifyFetchError's /permission denied/ → misreport as auth.
logPluginFetch('mcpb', url, 'success', performance.now() - started)
fetchTelemetryFired = true
// Save to disk (binary data)
await writeFile(destPath, Buffer.from(data))
@@ -523,15 +516,7 @@ async function downloadMcpb(
return data
} catch (error) {
if (!fetchTelemetryFired) {
logPluginFetch(
'mcpb',
url,
'failure',
performance.now() - started,
classifyFetchError(error),
)
}
void started
const errorMsg = errorMessage(error)
const fullError = new Error(
`Failed to download MCPB file from ${url}: ${errorMsg}`,

View File

@@ -85,7 +85,6 @@ import { SettingsSchema } from '../settings/types.js'
import { jsonParse, jsonStringify } from '../slowOperations.js'
import { getAddDirEnabledPlugins } from './addDirPluginSettings.js'
import { verifyAndDemote } from './dependencyResolver.js'
import { classifyFetchError, logPluginFetch } from './fetchTelemetry.js'
import { checkGitAvailable } from './gitAvailability.js'
import { getInMemoryInstalledPlugins } from './installedPluginsManager.js'
import { getManagedPluginNames } from './managedPlugins.js'
@@ -563,13 +562,6 @@ export async function gitClone(
const cloneResult = await execFileNoThrow(gitExe(), args)
if (cloneResult.code !== 0) {
logPluginFetch(
'plugin_clone',
gitUrl,
'failure',
performance.now() - cloneStarted,
classifyFetchError(cloneResult.stderr),
)
throw new Error(`Failed to clone repository: ${cloneResult.stderr}`)
}
@@ -595,13 +587,6 @@ export async function gitClone(
)
if (unshallowResult.code !== 0) {
logPluginFetch(
'plugin_clone',
gitUrl,
'failure',
performance.now() - cloneStarted,
classifyFetchError(unshallowResult.stderr),
)
throw new Error(
`Failed to fetch commit ${sha}: ${unshallowResult.stderr}`,
)
@@ -616,27 +601,12 @@ export async function gitClone(
)
if (checkoutResult.code !== 0) {
logPluginFetch(
'plugin_clone',
gitUrl,
'failure',
performance.now() - cloneStarted,
classifyFetchError(checkoutResult.stderr),
)
throw new Error(
`Failed to checkout commit ${sha}: ${checkoutResult.stderr}`,
)
}
}
// Fire success only after ALL network ops (clone + optional SHA fetch)
// complete — same telemetry-scope discipline as mcpb and marketplace_url.
logPluginFetch(
'plugin_clone',
gitUrl,
'success',
performance.now() - cloneStarted,
)
void cloneStarted
}
/**