fix(indexer): prevent token exhaustion and reserve 20% budget for website users
- Lower exhaustion threshold from <10 to <2 (stop wasting remaining tokens) - Refresh ALL tokens after wait instead of only one - Fix token/Octokit mismatch by returning token from getBestInstance() - Add budget checking (20% reserve) to deep-scan, discover-repos, awesome-lists, and full-enhanced commands - Add rate limit error handling with retry in DeepScanCrawler - Sync translation and query changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,14 +17,10 @@ export class DeepScanCrawler {
|
||||
this.octokitPool = new OctokitPool(this.tokenManager);
|
||||
}
|
||||
|
||||
private async getOctokit(): Promise<Octokit> {
|
||||
private async getOctokit(): Promise<{ octokit: Octokit; token: string }> {
|
||||
return this.octokitPool.getBestInstance();
|
||||
}
|
||||
|
||||
private getCurrentToken(): string {
|
||||
return this.tokenManager.getBestToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep scan a repository for all SKILL.md files using Git Trees API
|
||||
* This is more thorough than code search as it scans the entire repo
|
||||
@@ -34,8 +30,7 @@ export class DeepScanCrawler {
|
||||
|
||||
try {
|
||||
// Get repository info for default branch
|
||||
const octokit = await this.getOctokit();
|
||||
const token = this.getCurrentToken();
|
||||
const { octokit, token } = await this.getOctokit();
|
||||
const repoInfo = await octokit.repos.get({ owner, repo });
|
||||
this.octokitPool.updateStats(token, repoInfo.headers);
|
||||
|
||||
@@ -101,10 +96,23 @@ export class DeepScanCrawler {
|
||||
console.log(` Repository ${owner}/${repo} is too large, using fallback scan`);
|
||||
return this.fallbackScan(owner, repo);
|
||||
}
|
||||
if (this.isRateLimitError(error)) {
|
||||
console.log(` Rate limit hit scanning ${owner}/${repo}, waiting for token rotation...`);
|
||||
await this.tokenManager.checkAndRotate();
|
||||
// Retry once after rotation
|
||||
return this.scanRepository(owner, repo);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private isRateLimitError(error: unknown): boolean {
|
||||
if (!(error instanceof Error)) return false;
|
||||
const status = (error as { status?: number }).status;
|
||||
if (status === 403 || status === 429) return true;
|
||||
return error.message.includes('rate limit') || error.message.includes('secondary rate limit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback scan for large repositories - scan known skill directories
|
||||
*/
|
||||
@@ -114,8 +122,7 @@ export class DeepScanCrawler {
|
||||
|
||||
for (const basePath of knownPaths) {
|
||||
try {
|
||||
const octokit = await this.getOctokit();
|
||||
const token = this.getCurrentToken();
|
||||
const { octokit, token } = await this.getOctokit();
|
||||
|
||||
const response = await octokit.repos.getContent({
|
||||
owner,
|
||||
@@ -143,8 +150,7 @@ export class DeepScanCrawler {
|
||||
const dirs = response.data.filter((item) => item.type === 'dir');
|
||||
for (const dir of dirs) {
|
||||
try {
|
||||
const subOctokit = await this.getOctokit();
|
||||
const subToken = this.getCurrentToken();
|
||||
const { octokit: subOctokit, token: subToken } = await this.getOctokit();
|
||||
const subDir = await subOctokit.repos.getContent({
|
||||
owner,
|
||||
repo,
|
||||
@@ -223,8 +229,7 @@ export class DeepScanCrawler {
|
||||
*/
|
||||
async getFileContent(owner: string, repo: string, path: string): Promise<string | null> {
|
||||
try {
|
||||
const octokit = await this.getOctokit();
|
||||
const token = this.getCurrentToken();
|
||||
const { octokit, token } = await this.getOctokit();
|
||||
const response = await octokit.repos.getContent({
|
||||
owner,
|
||||
repo,
|
||||
|
||||
Reference in New Issue
Block a user