axios+telemetry cleanup
This commit is contained in:
@@ -12,6 +12,7 @@ import { installOrUpdateClaudePackage, localInstallationExists } from '../utils/
|
||||
import { removeInstalledSymlink } from '../utils/nativeInstaller/index.js';
|
||||
import { gt, gte } from '../utils/semver.js';
|
||||
import { getInitialSettings } from '../utils/settings/settings.js';
|
||||
import { VERSION, PACKAGE_URL } from '../constants/product.js';
|
||||
type Props = {
|
||||
isUpdating: boolean;
|
||||
onChangeIsUpdating: (isUpdating: boolean) => void;
|
||||
@@ -53,7 +54,7 @@ export function AutoUpdater({
|
||||
logForDebugging('AutoUpdater: Skipping update check in test/dev environment');
|
||||
return;
|
||||
}
|
||||
const currentVersion = MACRO.VERSION;
|
||||
const currentVersion = VERSION;
|
||||
const channel = getInitialSettings()?.autoUpdatesChannel ?? 'latest';
|
||||
let latestVersion = await getLatestVersion(channel);
|
||||
const isDisabled = isAutoUpdaterDisabled();
|
||||
@@ -190,7 +191,7 @@ export function AutoUpdater({
|
||||
{(autoUpdaterResult?.status === 'install_failed' || autoUpdaterResult?.status === 'no_permissions') && <Text color="error" wrap="truncate">
|
||||
✗ Auto-update failed · Try <Text bold>claude doctor</Text> or{' '}
|
||||
<Text bold>
|
||||
{hasLocalInstall ? `cd ~/.claude/local && npm update ${MACRO.PACKAGE_URL}` : `npm i -g ${MACRO.PACKAGE_URL}`}
|
||||
{hasLocalInstall ? `cd ~/.claude/local && npm update ${PACKAGE_URL}` : `npm i -g ${PACKAGE_URL}`}
|
||||
</Text>
|
||||
</Text>}
|
||||
</Box>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import axios from 'axios';
|
||||
import { isHttpError, nativeRequest } from '../utils/http.js';
|
||||
import { readFile, stat } from 'fs/promises';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
@@ -28,6 +28,7 @@ import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js';
|
||||
import { Byline } from './design-system/Byline.js';
|
||||
import { Dialog } from './design-system/Dialog.js';
|
||||
import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js';
|
||||
import { VERSION } from 'src/constants/product.js';
|
||||
import TextInput from './TextInput.js';
|
||||
|
||||
// This value was determined experimentally by testing the URL length limit
|
||||
@@ -211,7 +212,7 @@ export function Feedback({
|
||||
platform: env.platform,
|
||||
gitRepo: envInfo.isGit,
|
||||
terminal: env.terminal,
|
||||
version: MACRO.VERSION,
|
||||
version: VERSION,
|
||||
transcript: normalizeMessagesForAPI(messages),
|
||||
errors: sanitizedErrors,
|
||||
lastApiRequest: getLastAPIRequest(),
|
||||
@@ -343,7 +344,7 @@ export function Feedback({
|
||||
<Text>
|
||||
- Environment info:{' '}
|
||||
<Text dimColor>
|
||||
{env.platform}, {env.terminal}, v{MACRO.VERSION}
|
||||
{env.platform}, {env.terminal}, v{VERSION}
|
||||
</Text>
|
||||
</Text>
|
||||
{envInfo.gitState && <Text>
|
||||
@@ -396,7 +397,7 @@ export function createGitHubIssueUrl(feedbackId: string, title: string, descript
|
||||
}>): string {
|
||||
const sanitizedTitle = redactSensitiveInfo(title);
|
||||
const sanitizedDescription = redactSensitiveInfo(description);
|
||||
const bodyPrefix = `**Bug Description**\n${sanitizedDescription}\n\n` + `**Environment Info**\n` + `- Platform: ${env.platform}\n` + `- Terminal: ${env.terminal}\n` + `- Version: ${MACRO.VERSION || 'unknown'}\n` + `- Feedback ID: ${feedbackId}\n` + `\n**Errors**\n\`\`\`json\n`;
|
||||
const bodyPrefix = `**Bug Description**\n${sanitizedDescription}\n\n` + `**Environment Info**\n` + `- Platform: ${env.platform}\n` + `- Terminal: ${env.terminal}\n` + `- Version: ${VERSION || 'unknown'}\n` + `- Feedback ID: ${feedbackId}\n` + `\n**Errors**\n\`\`\`json\n`;
|
||||
const errorSuffix = `\n\`\`\`\n`;
|
||||
const errorsJson = jsonStringify(errors);
|
||||
const baseUrl = `${GITHUB_ISSUES_REPO_URL}/new?title=${encodeURIComponent(sanitizedTitle)}&labels=user-reported,bug&body=`;
|
||||
@@ -540,12 +541,13 @@ async function submitFeedback(data: FeedbackData, signal?: AbortSignal): Promise
|
||||
'User-Agent': getUserAgent(),
|
||||
...authResult.headers
|
||||
};
|
||||
const response = await axios.post('https://api.anthropic.com/api/claude_cli_feedback', {
|
||||
content: jsonStringify(data)
|
||||
}, {
|
||||
const response = await nativeRequest('https://api.anthropic.com/api/claude_cli_feedback', {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: {
|
||||
content: jsonStringify(data)
|
||||
},
|
||||
timeout: 30000,
|
||||
// 30 second timeout to prevent hanging
|
||||
signal
|
||||
});
|
||||
if (response.status === 200) {
|
||||
@@ -566,14 +568,13 @@ async function submitFeedback(data: FeedbackData, signal?: AbortSignal): Promise
|
||||
success: false
|
||||
};
|
||||
} catch (err) {
|
||||
// Handle cancellation/abort - don't log as error
|
||||
if (axios.isCancel(err)) {
|
||||
if (err instanceof Error && err.name === 'AbortError') {
|
||||
return {
|
||||
success: false
|
||||
};
|
||||
}
|
||||
if (axios.isAxiosError(err) && err.response?.status === 403) {
|
||||
const errorData = err.response.data;
|
||||
if (isHttpError(err) && err.status === 403) {
|
||||
const errorData = err.data;
|
||||
if (errorData?.error?.type === 'permission_error' && errorData?.error?.message?.includes('Custom data retention settings')) {
|
||||
sanitizeAndLogError(new Error('Cannot submit feedback because custom data retention settings are enabled'));
|
||||
return {
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
import axios from 'axios'
|
||||
import { readFile, stat } from 'fs/promises'
|
||||
import type { Message } from '../../types/message.js'
|
||||
import { checkAndRefreshOAuthTokenIfNeeded } from '../../utils/auth.js'
|
||||
import { logForDebugging } from '../../utils/debug.js'
|
||||
import { errorMessage } from '../../utils/errors.js'
|
||||
import { getAuthHeaders, getUserAgent } from '../../utils/http.js'
|
||||
import { normalizeMessagesForAPI } from '../../utils/messages.js'
|
||||
import {
|
||||
extractAgentIdsFromMessages,
|
||||
getTranscriptPath,
|
||||
loadSubagentTranscripts,
|
||||
MAX_TRANSCRIPT_READ_BYTES,
|
||||
} from '../../utils/sessionStorage.js'
|
||||
import { jsonStringify } from '../../utils/slowOperations.js'
|
||||
import { redactSensitiveInfo } from '../Feedback.js'
|
||||
/**
|
||||
* Transcript Share Service - Stubbed
|
||||
*
|
||||
* This service is stubbed to ensure no session transcripts or user
|
||||
* identification data is sent to external services, even during
|
||||
* feedback surveys.
|
||||
*/
|
||||
|
||||
type TranscriptShareResult = {
|
||||
export type TranscriptShareResult = {
|
||||
success: boolean
|
||||
transcriptId?: string
|
||||
}
|
||||
@@ -27,86 +18,11 @@ export type TranscriptShareTrigger =
|
||||
| 'memory_survey'
|
||||
|
||||
export async function submitTranscriptShare(
|
||||
messages: Message[],
|
||||
trigger: TranscriptShareTrigger,
|
||||
appearanceId: string,
|
||||
_messages: any[],
|
||||
_trigger: TranscriptShareTrigger,
|
||||
_appearanceId: string,
|
||||
): Promise<TranscriptShareResult> {
|
||||
try {
|
||||
logForDebugging('Collecting transcript for sharing', { level: 'info' })
|
||||
|
||||
const transcript = normalizeMessagesForAPI(messages)
|
||||
|
||||
// Collect subagent transcripts
|
||||
const agentIds = extractAgentIdsFromMessages(messages)
|
||||
const subagentTranscripts = await loadSubagentTranscripts(agentIds)
|
||||
|
||||
// Read raw JSONL transcript (with size guard to prevent OOM)
|
||||
let rawTranscriptJsonl: string | undefined
|
||||
try {
|
||||
const transcriptPath = getTranscriptPath()
|
||||
const { size } = await stat(transcriptPath)
|
||||
if (size <= MAX_TRANSCRIPT_READ_BYTES) {
|
||||
rawTranscriptJsonl = await readFile(transcriptPath, 'utf-8')
|
||||
} else {
|
||||
logForDebugging(
|
||||
`Skipping raw transcript read: file too large (${size} bytes)`,
|
||||
{ level: 'warn' },
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
// File may not exist
|
||||
}
|
||||
|
||||
const data = {
|
||||
trigger,
|
||||
version: MACRO.VERSION,
|
||||
platform: process.platform,
|
||||
transcript,
|
||||
subagentTranscripts:
|
||||
Object.keys(subagentTranscripts).length > 0
|
||||
? subagentTranscripts
|
||||
: undefined,
|
||||
rawTranscriptJsonl,
|
||||
}
|
||||
|
||||
const content = redactSensitiveInfo(jsonStringify(data))
|
||||
|
||||
await checkAndRefreshOAuthTokenIfNeeded()
|
||||
|
||||
const authResult = getAuthHeaders()
|
||||
if (authResult.error) {
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': getUserAgent(),
|
||||
...authResult.headers,
|
||||
}
|
||||
|
||||
const response = await axios.post(
|
||||
'https://api.anthropic.com/api/claude_code_shared_session_transcripts',
|
||||
{ content, appearance_id: appearanceId },
|
||||
{
|
||||
headers,
|
||||
timeout: 30000,
|
||||
},
|
||||
)
|
||||
|
||||
if (response.status === 200 || response.status === 201) {
|
||||
const result = response.data
|
||||
logForDebugging('Transcript shared successfully', { level: 'info' })
|
||||
return {
|
||||
success: true,
|
||||
transcriptId: result?.transcript_id,
|
||||
}
|
||||
}
|
||||
|
||||
return { success: false }
|
||||
} catch (err) {
|
||||
logForDebugging(errorMessage(err), {
|
||||
level: 'error',
|
||||
})
|
||||
return { success: false }
|
||||
}
|
||||
// Always return failure to prevent sharing data.
|
||||
// This effectively disables the feature without crashing the UI.
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import { EmergencyTip } from './EmergencyTip.js';
|
||||
import { VoiceModeNotice } from './VoiceModeNotice.js';
|
||||
import { Opus1mMergeNotice } from './Opus1mMergeNotice.js';
|
||||
import { feature } from 'bun:bundle';
|
||||
import { VERSION } from '../../constants/product.js';
|
||||
|
||||
// Conditional require so ChannelsNotice.tsx tree-shakes when both flags are
|
||||
// false. A module-scope helper component inside a feature() ternary does NOT
|
||||
@@ -92,7 +93,7 @@ export function LogoV2() {
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = () => {
|
||||
const currentConfig = getGlobalConfig();
|
||||
if (currentConfig.lastReleaseNotesSeen === MACRO.VERSION) {
|
||||
if (currentConfig.lastReleaseNotesSeen === VERSION) {
|
||||
return;
|
||||
}
|
||||
saveGlobalConfig(_temp3);
|
||||
@@ -526,12 +527,12 @@ export function LogoV2() {
|
||||
return t41;
|
||||
}
|
||||
function _temp3(current) {
|
||||
if (current.lastReleaseNotesSeen === MACRO.VERSION) {
|
||||
if (current.lastReleaseNotesSeen === VERSION) {
|
||||
return current;
|
||||
}
|
||||
return {
|
||||
...current,
|
||||
lastReleaseNotesSeen: MACRO.VERSION
|
||||
lastReleaseNotesSeen: VERSION
|
||||
};
|
||||
}
|
||||
function _temp2(s_0) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { c as _c } from "react/compiler-runtime";
|
||||
import React from 'react';
|
||||
import { Box, Text, useTheme } from 'src/ink.js';
|
||||
import { env } from '../../utils/env.js';
|
||||
import { VERSION } from '../../constants/product.js';
|
||||
const WELCOME_V2_WIDTH = 58;
|
||||
export function WelcomeV2() {
|
||||
const $ = _c(35);
|
||||
@@ -28,7 +29,7 @@ export function WelcomeV2() {
|
||||
let t7;
|
||||
let t8;
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <Text><Text color="claude">{"Welcome to Claude Code"} </Text><Text dimColor={true}>v{MACRO.VERSION} </Text></Text>;
|
||||
t0 = <Text><Text color="claude">{"Welcome to Claude Code"} </Text><Text dimColor={true}>v{VERSION} </Text></Text>;
|
||||
t1 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text>;
|
||||
t2 = <Text>{" "}</Text>;
|
||||
t3 = <Text>{" "}</Text>;
|
||||
@@ -113,7 +114,7 @@ export function WelcomeV2() {
|
||||
let t5;
|
||||
let t6;
|
||||
if ($[18] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <Text><Text color="claude">{"Welcome to Claude Code"} </Text><Text dimColor={true}>v{MACRO.VERSION} </Text></Text>;
|
||||
t0 = <Text><Text color="claude">{"Welcome to Claude Code"} </Text><Text dimColor={true}>v{VERSION} </Text></Text>;
|
||||
t1 = <Text>{"\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026"}</Text>;
|
||||
t2 = <Text>{" "}</Text>;
|
||||
t3 = <Text>{" * \u2588\u2588\u2588\u2588\u2588\u2593\u2593\u2591 "}</Text>;
|
||||
@@ -218,7 +219,7 @@ function AppleTerminalWelcomeV2(t0) {
|
||||
}
|
||||
let t2;
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = <Text dimColor={true}>v{MACRO.VERSION} </Text>;
|
||||
t2 = <Text dimColor={true}>v{VERSION} </Text>;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t2 = $[2];
|
||||
@@ -329,7 +330,7 @@ function AppleTerminalWelcomeV2(t0) {
|
||||
}
|
||||
let t2;
|
||||
if ($[24] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = <Text dimColor={true}>v{MACRO.VERSION} </Text>;
|
||||
t2 = <Text dimColor={true}>v{VERSION} </Text>;
|
||||
$[24] = t2;
|
||||
} else {
|
||||
t2 = $[24];
|
||||
|
||||
@@ -12,6 +12,7 @@ import { isAutoUpdaterDisabled } from '../utils/config.js';
|
||||
import { installLatest } from '../utils/nativeInstaller/index.js';
|
||||
import { gt } from '../utils/semver.js';
|
||||
import { getInitialSettings } from '../utils/settings/settings.js';
|
||||
import { VERSION } from '../constants/product.js';
|
||||
|
||||
/**
|
||||
* Categorize error messages for analytics
|
||||
@@ -89,12 +90,12 @@ export function NativeAutoUpdater({
|
||||
try {
|
||||
// Check if current version is above the max allowed version
|
||||
const maxVersion = await getMaxVersion();
|
||||
if (maxVersion && gt(MACRO.VERSION, maxVersion)) {
|
||||
if (maxVersion && gt(VERSION, maxVersion)) {
|
||||
const msg = await getMaxVersionMessage();
|
||||
setMaxVersionIssue(msg ?? 'affects your version');
|
||||
}
|
||||
const result = await installLatest(channel);
|
||||
const currentVersion = MACRO.VERSION;
|
||||
const currentVersion = VERSION;
|
||||
const latencyMs = Date.now() - startTime;
|
||||
|
||||
// Handle lock contention gracefully - just return without treating as error
|
||||
|
||||
Reference in New Issue
Block a user