Improve warning notice formatting (#1415)

This commit is contained in:
chioarub
2026-05-28 15:38:59 +08:00
committed by GitHub
parent 11d59ecdcb
commit fabb148fbb
2 changed files with 94 additions and 28 deletions
@@ -1,6 +1,10 @@
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
import type { StatusNoticeContext } from './statusNoticeDefinitions.js'
import { getActiveNotices } from './statusNoticeDefinitions.js'
import {
getActiveNotices,
statusNoticeDefinitions,
} from './statusNoticeDefinitions.js'
import { renderToString } from './staticRender.js'
// Regression coverage for issue #244 — the two safety-related status notices
// that warn 3P users when they are running without the AI classifier or with
@@ -21,6 +25,15 @@ function activeIds(ctx: StatusNoticeContext): string[] {
return getActiveNotices(ctx).map(n => n.id)
}
async function renderNoticePlainText(
id: string,
ctx: StatusNoticeContext,
): Promise<string> {
const notice = statusNoticeDefinitions.find(n => n.id === id)
expect(notice).toBeDefined()
return renderToString(notice!.render(ctx), 80)
}
const SAVED_ARGV = process.argv
const SAVED_API_KEY = process.env.ANTHROPIC_API_KEY
@@ -139,3 +152,42 @@ describe('dangerously-skip-permissions sandbox notice (#244 finding 2)', () => {
)
})
})
describe('safety notice rendering', () => {
test('separates warning icons from the notice text', async () => {
const ctx = buildContext({
permissionMode: 'bypassPermissions',
mainLoopModel: 'llama3.1',
})
const thirdPartyNotice = await renderNoticePlainText(
'third-party-permissive-mode',
ctx,
)
const dangerouslySkipNotice = await renderNoticePlainText(
'dangerously-skip-permissions-no-sandbox',
ctx,
)
expect(thirdPartyNotice).toContain('⚠ bypassPermissions')
expect(thirdPartyNotice).not.toContain('⚠bypassPermissions')
expect(dangerouslySkipNotice).toContain(
'⚠ --dangerously-skip-permissions',
)
expect(dangerouslySkipNotice).not.toContain(
'⚠--dangerously-skip-permissions',
)
expect(
thirdPartyNotice
.split('\n')
.slice(1)
.every(line => line.startsWith(' ')),
).toBe(true)
expect(
dangerouslySkipNotice
.split('\n')
.slice(1)
.every(line => line.startsWith(' ')),
).toBe(true)
})
})
+41 -27
View File
@@ -35,6 +35,23 @@ export type StatusNoticeDefinition = {
render: (context: StatusNoticeContext) => React.ReactNode;
};
function WarningNoticeRow({
children,
marginTop,
}: {
children: React.ReactNode;
marginTop?: number;
}): React.ReactNode {
return <Box flexDirection="row" marginTop={marginTop}>
<Box marginRight={1}>
<Text color="warning">{figures.warning}</Text>
</Box>
<Box flexDirection="column" flexShrink={1}>
{children}
</Box>
</Box>;
}
// Individual notice definitions
const largeMemoryFilesNotice: StatusNoticeDefinition = {
id: 'large-memory-files',
@@ -45,15 +62,14 @@ const largeMemoryFilesNotice: StatusNoticeDefinition = {
return <>
{largeMemoryFiles.map(file => {
const displayPath = file.path.startsWith(getCwd()) ? relative(getCwd(), file.path) : file.path;
return <Box key={file.path} flexDirection="row">
<Text color="warning">{figures.warning}</Text>
return <WarningNoticeRow key={file.path}>
<Text color="warning">
Large <Text bold>{displayPath}</Text> will impact performance (
{formatNumber(file.content.length)} chars &gt;{' '}
{formatNumber(MAX_MEMORY_CHARACTER_COUNT)})
<Text dimColor> · /memory to edit</Text>
</Text>
</Box>;
</WarningNoticeRow>;
})}
</>;
}
@@ -67,14 +83,13 @@ const claudeAiSubscriberExternalTokenNotice: StatusNoticeDefinition = {
},
render: () => {
const authTokenInfo = getAuthTokenSource();
return <Box flexDirection="row" marginTop={1}>
<Text color="warning">{figures.warning}</Text>
return <WarningNoticeRow marginTop={1}>
<Text color="warning">
Auth conflict: Using {authTokenInfo.source} instead of Claude account
subscription token. Either unset {authTokenInfo.source}, or run
`claude /logout`.
</Text>
</Box>;
</WarningNoticeRow>;
}
};
const apiKeyConflictNotice: StatusNoticeDefinition = {
@@ -94,13 +109,12 @@ const apiKeyConflictNotice: StatusNoticeDefinition = {
} = getAnthropicApiKeyWithSource({
skipRetrievingKeyFromApiKeyHelper: true
});
return <Box flexDirection="row" marginTop={1}>
<Text color="warning">{figures.warning}</Text>
return <WarningNoticeRow marginTop={1}>
<Text color="warning">
Auth conflict: Using {apiKeySource} instead of Anthropic Console key.
Either unset {apiKeySource}, or run `openclaude /logout`.
</Text>
</Box>;
</WarningNoticeRow>;
}
};
const bothAuthMethodsNotice: StatusNoticeDefinition = {
@@ -123,13 +137,12 @@ const bothAuthMethodsNotice: StatusNoticeDefinition = {
});
const authTokenInfo = getAuthTokenSource();
return <Box flexDirection="column" marginTop={1}>
<Box flexDirection="row">
<Text color="warning">{figures.warning}</Text>
<WarningNoticeRow>
<Text color="warning">
Auth conflict: Both a token ({authTokenInfo.source}) and an API key
({apiKeySource}) are set. This may lead to unexpected behavior.
</Text>
</Box>
</WarningNoticeRow>
<Box flexDirection="column" marginLeft={3}>
<Text color="warning">
· Trying to use{' '}
@@ -154,15 +167,14 @@ const largeAgentDescriptionsNotice: StatusNoticeDefinition = {
},
render: context => {
const totalTokens = getAgentDescriptionsTotalTokens(context.agentDefinitions);
return <Box flexDirection="row">
<Text color="warning">{figures.warning}</Text>
return <WarningNoticeRow>
<Text color="warning">
Large cumulative agent descriptions will impact performance (~
{formatNumber(totalTokens)} tokens &gt;{' '}
{formatNumber(AGENT_DESCRIPTIONS_THRESHOLD)})
<Text dimColor> · /agents to manage</Text>
</Text>
</Box>;
</WarningNoticeRow>;
}
};
const jetbrainsPluginNotice: StatusNoticeDefinition = {
@@ -224,14 +236,15 @@ const thirdPartyPermissiveModeNotice: StatusNoticeDefinition = {
},
render: ctx => {
const mode = ctx.permissionMode;
return <Box flexDirection="row">
<Text color="warning">{figures.warning}</Text>
return <WarningNoticeRow>
<Text color="warning">
<Text bold>{mode}</Text> mode is active on a third-party provider —
tool calls run without the AI safety classifier.
<Text dimColor> Inspect tool calls manually, especially when working with untrusted code.</Text>
<Text bold>{mode}</Text> mode is active on a third-party provider.
</Text>
</Box>;
<Text dimColor>
Tool calls run without the AI safety classifier. Inspect tool calls manually,
especially when working with untrusted code.
</Text>
</WarningNoticeRow>;
}
};
// `--dangerously-skip-permissions` (a.k.a. bypassPermissions) auto-approves
@@ -250,14 +263,15 @@ const dangerouslySkipPermissionsNotice: StatusNoticeDefinition = {
isActive: ctx =>
hasDangerouslySkipPermissionsArg() ||
ctx.permissionMode === 'bypassPermissions',
render: () => <Box flexDirection="row">
<Text color="warning">{figures.warning}</Text>
render: () => <WarningNoticeRow>
<Text color="warning">
<Text bold>--dangerously-skip-permissions</Text> bypasses every tool
consent check.
<Text dimColor> Only use inside a sandbox with no internet access. Restart without the flag to re-enable prompts.</Text>
<Text bold>--dangerously-skip-permissions</Text> is active.
</Text>
</Box>
<Text dimColor>
Every tool consent check is bypassed. Only use inside a sandbox with no internet access.
Restart without the flag to re-enable prompts.
</Text>
</WarningNoticeRow>
};
// All notice definitions