mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
feat: 新增Claude Console账户临时封禁处理和错误消息清理
- 新增 CLAUDE_CONSOLE_BLOCKED_HANDLING_MINUTES 配置项,自动处理账户临时禁用的 400 错误(如 "organization has been disabled"、"too many active sessions" 等)。 - 添加 errorSanitizer 工具模块,自动清理上游错误响应中的供应商特定信息(URL、供应商名称等),避免泄露中转服务商信息。 - 统一调度器现在会主动检查并恢复已过期的封禁账户,确保账户在临时封禁时长结束后可以立即重新使用。
This commit is contained in:
@@ -36,6 +36,20 @@ class ClaudeConsoleAccountService {
|
||||
)
|
||||
}
|
||||
|
||||
_getBlockedHandlingMinutes() {
|
||||
const raw = process.env.CLAUDE_CONSOLE_BLOCKED_HANDLING_MINUTES
|
||||
if (raw === undefined || raw === null || raw === '') {
|
||||
return 0
|
||||
}
|
||||
|
||||
const parsed = Number.parseInt(raw, 10)
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
// 🏢 创建Claude Console账户
|
||||
async createAccount(options = {}) {
|
||||
const {
|
||||
@@ -690,6 +704,183 @@ class ClaudeConsoleAccountService {
|
||||
}
|
||||
}
|
||||
|
||||
// 🚫 标记账号为临时封禁状态(400错误 - 账户临时禁用)
|
||||
async markConsoleAccountBlocked(accountId, errorDetails = '') {
|
||||
try {
|
||||
const client = redis.getClientSafe()
|
||||
const account = await this.getAccount(accountId)
|
||||
|
||||
if (!account) {
|
||||
throw new Error('Account not found')
|
||||
}
|
||||
|
||||
const blockedMinutes = this._getBlockedHandlingMinutes()
|
||||
|
||||
if (blockedMinutes <= 0) {
|
||||
logger.info(
|
||||
`ℹ️ CLAUDE_CONSOLE_BLOCKED_HANDLING_MINUTES 未设置或为0,跳过账户封禁:${account.name} (${accountId})`
|
||||
)
|
||||
|
||||
if (account.blockedStatus === 'blocked') {
|
||||
try {
|
||||
await this.removeAccountBlocked(accountId)
|
||||
} catch (cleanupError) {
|
||||
logger.warn(`⚠️ 尝试移除账户封禁状态失败:${accountId}`, cleanupError)
|
||||
}
|
||||
}
|
||||
|
||||
return { success: false, skipped: true }
|
||||
}
|
||||
|
||||
const updates = {
|
||||
blockedAt: new Date().toISOString(),
|
||||
blockedStatus: 'blocked',
|
||||
isActive: 'false', // 禁用账户(与429保持一致)
|
||||
schedulable: 'false', // 停止调度(与429保持一致)
|
||||
status: 'account_blocked', // 设置状态(与429保持一致)
|
||||
errorMessage: '账户临时被禁用(400错误)',
|
||||
// 使用独立的封禁自动停止标记
|
||||
blockedAutoStopped: 'true'
|
||||
}
|
||||
|
||||
await client.hset(`${this.ACCOUNT_KEY_PREFIX}${accountId}`, updates)
|
||||
|
||||
// 发送Webhook通知,包含完整错误详情
|
||||
try {
|
||||
const webhookNotifier = require('../utils/webhookNotifier')
|
||||
await webhookNotifier.sendAccountAnomalyNotification({
|
||||
accountId,
|
||||
accountName: account.name || 'Claude Console Account',
|
||||
platform: 'claude-console',
|
||||
status: 'error',
|
||||
errorCode: 'CLAUDE_CONSOLE_BLOCKED',
|
||||
reason: `账户临时被禁用(400错误)。账户将在 ${blockedMinutes} 分钟后自动恢复。`,
|
||||
errorDetails: errorDetails || '无错误详情',
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
} catch (webhookError) {
|
||||
logger.error('Failed to send blocked webhook notification:', webhookError)
|
||||
}
|
||||
|
||||
logger.warn(`🚫 Claude Console account temporarily blocked: ${account.name} (${accountId})`)
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
logger.error(`❌ Failed to mark Claude Console account as blocked: ${accountId}`, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 移除账号的临时封禁状态
|
||||
async removeAccountBlocked(accountId) {
|
||||
try {
|
||||
const client = redis.getClientSafe()
|
||||
const accountKey = `${this.ACCOUNT_KEY_PREFIX}${accountId}`
|
||||
|
||||
// 获取账户当前状态和额度信息
|
||||
const [currentStatus, quotaStoppedAt] = await client.hmget(
|
||||
accountKey,
|
||||
'status',
|
||||
'quotaStoppedAt'
|
||||
)
|
||||
|
||||
// 删除封禁相关字段
|
||||
await client.hdel(accountKey, 'blockedAt', 'blockedStatus')
|
||||
|
||||
// 根据不同情况决定是否恢复账户
|
||||
if (currentStatus === 'account_blocked') {
|
||||
if (quotaStoppedAt) {
|
||||
// 还有额度限制,改为quota_exceeded状态
|
||||
await client.hset(accountKey, {
|
||||
status: 'quota_exceeded'
|
||||
// isActive保持false
|
||||
})
|
||||
logger.info(
|
||||
`⚠️ Blocked status removed but quota exceeded remains for account: ${accountId}`
|
||||
)
|
||||
} else {
|
||||
// 没有额度限制,完全恢复
|
||||
const accountData = await client.hgetall(accountKey)
|
||||
const updateData = {
|
||||
isActive: 'true',
|
||||
status: 'active',
|
||||
errorMessage: ''
|
||||
}
|
||||
|
||||
const hadAutoStop = accountData.blockedAutoStopped === 'true'
|
||||
|
||||
// 只恢复因封禁而自动停止的账户
|
||||
if (hadAutoStop && accountData.schedulable === 'false') {
|
||||
updateData.schedulable = 'true' // 恢复调度
|
||||
logger.info(
|
||||
`✅ Auto-resuming scheduling for Claude Console account ${accountId} after blocked status cleared`
|
||||
)
|
||||
}
|
||||
|
||||
if (hadAutoStop) {
|
||||
await client.hdel(accountKey, 'blockedAutoStopped')
|
||||
}
|
||||
|
||||
await client.hset(accountKey, updateData)
|
||||
logger.success(`✅ Blocked status removed and account re-enabled: ${accountId}`)
|
||||
}
|
||||
} else {
|
||||
if (await client.hdel(accountKey, 'blockedAutoStopped')) {
|
||||
logger.info(
|
||||
`ℹ️ Removed stale auto-stop flag for Claude Console account ${accountId} during blocked status recovery`
|
||||
)
|
||||
}
|
||||
logger.success(`✅ Blocked status removed for Claude Console account: ${accountId}`)
|
||||
}
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`❌ Failed to remove blocked status for Claude Console account: ${accountId}`,
|
||||
error
|
||||
)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 🔍 检查账号是否处于临时封禁状态
|
||||
async isAccountBlocked(accountId) {
|
||||
try {
|
||||
const account = await this.getAccount(accountId)
|
||||
if (!account) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (account.blockedStatus === 'blocked' && account.blockedAt) {
|
||||
const blockedDuration = this._getBlockedHandlingMinutes()
|
||||
|
||||
if (blockedDuration <= 0) {
|
||||
await this.removeAccountBlocked(accountId)
|
||||
return false
|
||||
}
|
||||
|
||||
const blockedAt = new Date(account.blockedAt)
|
||||
const now = new Date()
|
||||
const minutesSinceBlocked = (now - blockedAt) / (1000 * 60)
|
||||
|
||||
// 禁用时长过后自动恢复
|
||||
if (minutesSinceBlocked >= blockedDuration) {
|
||||
await this.removeAccountBlocked(accountId)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`❌ Failed to check blocked status for Claude Console account: ${accountId}`,
|
||||
error
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 🚫 标记账号为过载状态(529错误)
|
||||
async markAccountOverloaded(accountId) {
|
||||
try {
|
||||
|
||||
@@ -2,6 +2,11 @@ const axios = require('axios')
|
||||
const claudeConsoleAccountService = require('./claudeConsoleAccountService')
|
||||
const logger = require('../utils/logger')
|
||||
const config = require('../../config/config')
|
||||
const {
|
||||
sanitizeUpstreamError,
|
||||
sanitizeErrorMessage,
|
||||
isAccountDisabledError
|
||||
} = require('../utils/errorSanitizer')
|
||||
|
||||
class ClaudeConsoleRelayService {
|
||||
constructor() {
|
||||
@@ -172,14 +177,49 @@ class ClaudeConsoleRelayService {
|
||||
logger.debug(
|
||||
`[DEBUG] Response data length: ${response.data ? (typeof response.data === 'string' ? response.data.length : JSON.stringify(response.data).length) : 0}`
|
||||
)
|
||||
logger.debug(
|
||||
`[DEBUG] Response data preview: ${typeof response.data === 'string' ? response.data.substring(0, 200) : JSON.stringify(response.data).substring(0, 200)}`
|
||||
)
|
||||
|
||||
// 对于错误响应,记录原始错误和清理后的预览
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
// 记录原始错误响应(包含供应商信息,用于调试)
|
||||
const rawData =
|
||||
typeof response.data === 'string' ? response.data : JSON.stringify(response.data)
|
||||
logger.error(
|
||||
`📝 Upstream error response from ${account?.name || accountId}: ${rawData.substring(0, 500)}`
|
||||
)
|
||||
|
||||
// 记录清理后的数据到error
|
||||
try {
|
||||
const responseData =
|
||||
typeof response.data === 'string' ? JSON.parse(response.data) : response.data
|
||||
const sanitizedData = sanitizeUpstreamError(responseData)
|
||||
logger.error(`🧹 [SANITIZED] Error response to client: ${JSON.stringify(sanitizedData)}`)
|
||||
} catch (e) {
|
||||
const rawText =
|
||||
typeof response.data === 'string' ? response.data : JSON.stringify(response.data)
|
||||
const sanitizedText = sanitizeErrorMessage(rawText)
|
||||
logger.error(`🧹 [SANITIZED] Error response to client: ${sanitizedText}`)
|
||||
}
|
||||
} else {
|
||||
logger.debug(
|
||||
`[DEBUG] Response data preview: ${typeof response.data === 'string' ? response.data.substring(0, 200) : JSON.stringify(response.data).substring(0, 200)}`
|
||||
)
|
||||
}
|
||||
|
||||
// 检查是否为账户禁用/不可用的 400 错误
|
||||
const accountDisabledError = isAccountDisabledError(response.status, response.data)
|
||||
|
||||
// 检查错误状态并相应处理
|
||||
if (response.status === 401) {
|
||||
logger.warn(`🚫 Unauthorized error detected for Claude Console account ${accountId}`)
|
||||
await claudeConsoleAccountService.markAccountUnauthorized(accountId)
|
||||
} else if (accountDisabledError) {
|
||||
logger.error(
|
||||
`🚫 Account disabled error (400) detected for Claude Console account ${accountId}, marking as blocked`
|
||||
)
|
||||
// 传入完整的错误详情到 webhook
|
||||
const errorDetails =
|
||||
typeof response.data === 'string' ? response.data : JSON.stringify(response.data)
|
||||
await claudeConsoleAccountService.markConsoleAccountBlocked(accountId, errorDetails)
|
||||
} else if (response.status === 429) {
|
||||
logger.warn(`🚫 Rate limit detected for Claude Console account ${accountId}`)
|
||||
// 收到429先检查是否因为超过了手动配置的每日额度
|
||||
@@ -206,9 +246,30 @@ class ClaudeConsoleRelayService {
|
||||
// 更新最后使用时间
|
||||
await this._updateLastUsedTime(accountId)
|
||||
|
||||
const responseBody =
|
||||
typeof response.data === 'string' ? response.data : JSON.stringify(response.data)
|
||||
logger.debug(`[DEBUG] Final response body to return: ${responseBody}`)
|
||||
// 准备响应体并清理错误信息(如果是错误响应)
|
||||
let responseBody
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
// 错误响应,清理供应商信息
|
||||
try {
|
||||
const responseData =
|
||||
typeof response.data === 'string' ? JSON.parse(response.data) : response.data
|
||||
const sanitizedData = sanitizeUpstreamError(responseData)
|
||||
responseBody = JSON.stringify(sanitizedData)
|
||||
logger.debug(`🧹 Sanitized error response`)
|
||||
} catch (parseError) {
|
||||
// 如果无法解析为JSON,尝试清理文本
|
||||
const rawText =
|
||||
typeof response.data === 'string' ? response.data : JSON.stringify(response.data)
|
||||
responseBody = sanitizeErrorMessage(rawText)
|
||||
logger.debug(`🧹 Sanitized error text`)
|
||||
}
|
||||
} else {
|
||||
// 成功响应,不需要清理
|
||||
responseBody =
|
||||
typeof response.data === 'string' ? response.data : JSON.stringify(response.data)
|
||||
}
|
||||
|
||||
logger.debug(`[DEBUG] Final response body to return: ${responseBody.substring(0, 200)}...`)
|
||||
|
||||
return {
|
||||
statusCode: response.status,
|
||||
@@ -388,44 +449,83 @@ class ClaudeConsoleRelayService {
|
||||
`❌ Claude Console API returned error status: ${response.status} | Account: ${account?.name || accountId}`
|
||||
)
|
||||
|
||||
if (response.status === 401) {
|
||||
claudeConsoleAccountService.markAccountUnauthorized(accountId)
|
||||
} else if (response.status === 429) {
|
||||
claudeConsoleAccountService.markAccountRateLimited(accountId)
|
||||
// 检查是否因为超过每日额度
|
||||
claudeConsoleAccountService.checkQuotaUsage(accountId).catch((err) => {
|
||||
logger.error('❌ Failed to check quota after 429 error:', err)
|
||||
})
|
||||
} else if (response.status === 529) {
|
||||
claudeConsoleAccountService.markAccountOverloaded(accountId)
|
||||
}
|
||||
// 收集错误数据用于检测
|
||||
let errorDataForCheck = ''
|
||||
const errorChunks = []
|
||||
|
||||
// 设置错误响应的状态码和响应头
|
||||
if (!responseStream.headersSent) {
|
||||
const errorHeaders = {
|
||||
'Content-Type': response.headers['content-type'] || 'application/json',
|
||||
'Cache-Control': 'no-cache',
|
||||
Connection: 'keep-alive'
|
||||
}
|
||||
// 避免 Transfer-Encoding 冲突,让 Express 自动处理
|
||||
delete errorHeaders['Transfer-Encoding']
|
||||
delete errorHeaders['Content-Length']
|
||||
responseStream.writeHead(response.status, errorHeaders)
|
||||
}
|
||||
|
||||
// 直接透传错误数据,不进行包装
|
||||
response.data.on('data', (chunk) => {
|
||||
if (!responseStream.destroyed) {
|
||||
responseStream.write(chunk)
|
||||
}
|
||||
errorChunks.push(chunk)
|
||||
errorDataForCheck += chunk.toString()
|
||||
})
|
||||
|
||||
response.data.on('end', () => {
|
||||
if (!responseStream.destroyed) {
|
||||
responseStream.end()
|
||||
response.data.on('end', async () => {
|
||||
// 记录原始错误消息到日志(方便调试,包含供应商信息)
|
||||
logger.error(
|
||||
`📝 [Stream] Upstream error response from ${account?.name || accountId}: ${errorDataForCheck.substring(0, 500)}`
|
||||
)
|
||||
|
||||
// 检查是否为账户禁用错误
|
||||
const accountDisabledError = isAccountDisabledError(
|
||||
response.status,
|
||||
errorDataForCheck
|
||||
)
|
||||
|
||||
if (response.status === 401) {
|
||||
await claudeConsoleAccountService.markAccountUnauthorized(accountId)
|
||||
} else if (accountDisabledError) {
|
||||
logger.error(
|
||||
`🚫 [Stream] Account disabled error (400) detected for Claude Console account ${accountId}, marking as blocked`
|
||||
)
|
||||
// 传入完整的错误详情到 webhook
|
||||
await claudeConsoleAccountService.markConsoleAccountBlocked(
|
||||
accountId,
|
||||
errorDataForCheck
|
||||
)
|
||||
} else if (response.status === 429) {
|
||||
await claudeConsoleAccountService.markAccountRateLimited(accountId)
|
||||
// 检查是否因为超过每日额度
|
||||
claudeConsoleAccountService.checkQuotaUsage(accountId).catch((err) => {
|
||||
logger.error('❌ Failed to check quota after 429 error:', err)
|
||||
})
|
||||
} else if (response.status === 529) {
|
||||
await claudeConsoleAccountService.markAccountOverloaded(accountId)
|
||||
}
|
||||
|
||||
// 设置响应头
|
||||
if (!responseStream.headersSent) {
|
||||
responseStream.writeHead(response.status, {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'no-cache'
|
||||
})
|
||||
}
|
||||
|
||||
// 清理并发送错误响应
|
||||
try {
|
||||
const fullErrorData = Buffer.concat(errorChunks).toString()
|
||||
const errorJson = JSON.parse(fullErrorData)
|
||||
const sanitizedError = sanitizeUpstreamError(errorJson)
|
||||
|
||||
// 记录清理后的错误消息(发送给客户端的,完整记录)
|
||||
logger.error(
|
||||
`🧹 [Stream] [SANITIZED] Error response to client: ${JSON.stringify(sanitizedError)}`
|
||||
)
|
||||
|
||||
if (!responseStream.destroyed) {
|
||||
responseStream.write(JSON.stringify(sanitizedError))
|
||||
responseStream.end()
|
||||
}
|
||||
} catch (parseError) {
|
||||
const sanitizedText = sanitizeErrorMessage(errorDataForCheck)
|
||||
logger.error(`🧹 [Stream] [SANITIZED] Error response to client: ${sanitizedText}`)
|
||||
|
||||
if (!responseStream.destroyed) {
|
||||
responseStream.write(sanitizedText)
|
||||
responseStream.end()
|
||||
}
|
||||
}
|
||||
resolve() // 不抛出异常,正常完成流处理
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -527,68 +527,86 @@ class UnifiedClaudeScheduler {
|
||||
logger.info(`📋 Found ${consoleAccounts.length} total Claude Console accounts`)
|
||||
|
||||
for (const account of consoleAccounts) {
|
||||
// 主动检查封禁状态并尝试恢复(在过滤之前执行,确保可以恢复被封禁的账户)
|
||||
const wasBlocked = await claudeConsoleAccountService.isAccountBlocked(account.id)
|
||||
|
||||
// 如果账户之前被封禁但现在已恢复,重新获取最新状态
|
||||
let currentAccount = account
|
||||
if (wasBlocked === false && account.status === 'account_blocked') {
|
||||
// 可能刚刚被恢复,重新获取账户状态
|
||||
const freshAccount = await claudeConsoleAccountService.getAccount(account.id)
|
||||
if (freshAccount) {
|
||||
currentAccount = freshAccount
|
||||
logger.info(`🔄 Account ${account.name} was recovered from blocked status`)
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`🔍 Checking Claude Console account: ${account.name} - isActive: ${account.isActive}, status: ${account.status}, accountType: ${account.accountType}, schedulable: ${account.schedulable}`
|
||||
`🔍 Checking Claude Console account: ${currentAccount.name} - isActive: ${currentAccount.isActive}, status: ${currentAccount.status}, accountType: ${currentAccount.accountType}, schedulable: ${currentAccount.schedulable}`
|
||||
)
|
||||
|
||||
// 注意:getAllAccounts返回的isActive是布尔值
|
||||
// 注意:getAllAccounts返回的isActive是布尔值,getAccount返回的也是布尔值
|
||||
if (
|
||||
account.isActive === true &&
|
||||
account.status === 'active' &&
|
||||
account.accountType === 'shared' &&
|
||||
this._isSchedulable(account.schedulable)
|
||||
currentAccount.isActive === true &&
|
||||
currentAccount.status === 'active' &&
|
||||
currentAccount.accountType === 'shared' &&
|
||||
this._isSchedulable(currentAccount.schedulable)
|
||||
) {
|
||||
// 检查是否可调度
|
||||
|
||||
// 检查模型支持
|
||||
if (!this._isModelSupportedByAccount(account, 'claude-console', requestedModel)) {
|
||||
if (!this._isModelSupportedByAccount(currentAccount, 'claude-console', requestedModel)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查订阅是否过期
|
||||
if (claudeConsoleAccountService.isSubscriptionExpired(account)) {
|
||||
if (claudeConsoleAccountService.isSubscriptionExpired(currentAccount)) {
|
||||
logger.debug(
|
||||
`⏰ Claude Console account ${account.name} (${account.id}) expired at ${account.subscriptionExpiresAt}`
|
||||
`⏰ Claude Console account ${currentAccount.name} (${currentAccount.id}) expired at ${currentAccount.subscriptionExpiresAt}`
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
// 主动触发一次额度检查,确保状态即时生效
|
||||
try {
|
||||
await claudeConsoleAccountService.checkQuotaUsage(account.id)
|
||||
await claudeConsoleAccountService.checkQuotaUsage(currentAccount.id)
|
||||
} catch (e) {
|
||||
logger.warn(
|
||||
`Failed to check quota for Claude Console account ${account.name}: ${e.message}`
|
||||
`Failed to check quota for Claude Console account ${currentAccount.name}: ${e.message}`
|
||||
)
|
||||
// 继续处理该账号
|
||||
}
|
||||
|
||||
// 检查是否被限流
|
||||
const isRateLimited = await claudeConsoleAccountService.isAccountRateLimited(account.id)
|
||||
const isQuotaExceeded = await claudeConsoleAccountService.isAccountQuotaExceeded(account.id)
|
||||
const isRateLimited = await claudeConsoleAccountService.isAccountRateLimited(
|
||||
currentAccount.id
|
||||
)
|
||||
const isQuotaExceeded = await claudeConsoleAccountService.isAccountQuotaExceeded(
|
||||
currentAccount.id
|
||||
)
|
||||
|
||||
if (!isRateLimited && !isQuotaExceeded) {
|
||||
availableAccounts.push({
|
||||
...account,
|
||||
accountId: account.id,
|
||||
...currentAccount,
|
||||
accountId: currentAccount.id,
|
||||
accountType: 'claude-console',
|
||||
priority: parseInt(account.priority) || 50,
|
||||
lastUsedAt: account.lastUsedAt || '0'
|
||||
priority: parseInt(currentAccount.priority) || 50,
|
||||
lastUsedAt: currentAccount.lastUsedAt || '0'
|
||||
})
|
||||
logger.info(
|
||||
`✅ Added Claude Console account to available pool: ${account.name} (priority: ${account.priority})`
|
||||
`✅ Added Claude Console account to available pool: ${currentAccount.name} (priority: ${currentAccount.priority})`
|
||||
)
|
||||
} else {
|
||||
if (isRateLimited) {
|
||||
logger.warn(`⚠️ Claude Console account ${account.name} is rate limited`)
|
||||
logger.warn(`⚠️ Claude Console account ${currentAccount.name} is rate limited`)
|
||||
}
|
||||
if (isQuotaExceeded) {
|
||||
logger.warn(`💰 Claude Console account ${account.name} quota exceeded`)
|
||||
logger.warn(`💰 Claude Console account ${currentAccount.name} quota exceeded`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.info(
|
||||
`❌ Claude Console account ${account.name} not eligible - isActive: ${account.isActive}, status: ${account.status}, accountType: ${account.accountType}, schedulable: ${account.schedulable}`
|
||||
`❌ Claude Console account ${currentAccount.name} not eligible - isActive: ${currentAccount.isActive}, status: ${currentAccount.status}, accountType: ${currentAccount.accountType}, schedulable: ${currentAccount.schedulable}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user