# Instructions - Following Playwright test failed. - Explain why, be concise, respect Playwright best practices. - Provide a snippet of code with the fix, if possible. # Test info - Name: tssc/full_workflow.test.ts >> TSSC Complete Workflow >> Component Creation >> should create a component successfully - Location: tests/tssc/full_workflow.test.ts:45:5 # Error details ``` Error: Failed to cancel pipelines: HTTP-Code: 429 - Rate limiting error: HTTP-Code: 429 Message: Unknown API Status Code! Body: "{\"kind\":\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"status\":\"Failure\",\"message\":\"storage is (re)initializing\",\"reason\":\"TooManyRequests\",\"details\":{\"retryAfterSeconds\":1},\"code\":429}\n" Headers: {"audit-id":"75a6e485-fa8d-4b25-aaf4-20f32f1c9849","cache-control":"no-cache, private","connection":"close","content-length":"181","content-type":"application/json","date":"Sat, 09 May 2026 19:26:49 GMT","retry-after":"1","strict-transport-security":"max-age=31536000; includeSubDomains; preload","x-kubernetes-pf-flowschema-uid":"c7815538-882d-4a6d-85f7-0ff65d45e88f","x-kubernetes-pf-prioritylevel-uid":"80d9edfe-15c0-4d89-92d2-a3414c41f33e"} ``` # Test source ```ts 385 | ); 386 | return `https://${tektonWebhookUrl}`; 387 | } 388 | 389 | public override async getIntegrationSecret(): Promise> { 390 | throw new Error( 391 | 'Tekton does not support integration secrets in the same way as other CI systems.' 392 | ); 393 | } 394 | 395 | public override async getCIFilePathInRepo(): Promise { 396 | return '.tekton'; 397 | } 398 | 399 | public override async getPipelineLogs(pipeline: Pipeline): Promise { 400 | if (!pipeline.name) { 401 | throw new Error('Pipeline name is required for Tekton pipelines'); 402 | } 403 | 404 | try { 405 | const logs = await this.tektonClient.getPipelineRunLogs(TektonCI.CI_NAMESPACE, pipeline.name); 406 | if (!logs) { 407 | throw new Error(`No logs found for pipeline: ${pipeline.name}`); 408 | } 409 | return logs; 410 | } catch (error) { 411 | this.logger.error(`Error getting pipeline logs for ${pipeline.name}: ${error}`); 412 | throw new Error(`Failed to get pipeline logs: ${error}`); 413 | } 414 | } 415 | 416 | /** 417 | * Cancel all pipelines for this component with optional filtering 418 | */ 419 | public override async cancelAllPipelines( 420 | options?: CancelPipelineOptions 421 | ): Promise { 422 | // 1. Normalize options with defaults 423 | const opts = this.normalizeOptions(options); 424 | 425 | // 2. Initialize result object 426 | const result: MutableCancelResult = { 427 | total: 0, 428 | cancelled: 0, 429 | failed: 0, 430 | skipped: 0, 431 | details: [], 432 | errors: [], 433 | }; 434 | 435 | this.logger.info(`[Tekton] Starting pipeline cancellation for ${this.componentName}`); 436 | 437 | try { 438 | // 3. Fetch all PipelineRuns from Tekton API 439 | const allPipelineRuns = await this.fetchAllPipelineRuns(); 440 | result.total = allPipelineRuns.length; 441 | 442 | if (allPipelineRuns.length === 0) { 443 | this.logger.info(`[Tekton] No PipelineRuns found for ${this.componentName}`); 444 | return result; 445 | } 446 | 447 | this.logger.info(`[Tekton] Found ${allPipelineRuns.length} total PipelineRuns`); 448 | 449 | // 4. Apply filters 450 | const pipelineRunsToCancel = this.filterPipelineRuns(allPipelineRuns, opts); 451 | 452 | this.logger.info(`[Tekton] ${pipelineRunsToCancel.length} PipelineRuns match filters`); 453 | this.logger.info(`[Tekton] ${allPipelineRuns.length - pipelineRunsToCancel.length} PipelineRuns filtered out`); 454 | 455 | // 5. Cancel PipelineRuns in batches 456 | await this.cancelPipelineRunsInBatches(pipelineRunsToCancel, opts, result); 457 | 458 | // 6. Validate result counts (accounting invariant) 459 | const accounted = result.cancelled + result.failed + result.skipped; 460 | if (accounted !== result.total) { 461 | const missing = result.total - accounted; 462 | this.logger.error( 463 | `❌ [Tekton] ACCOUNTING ERROR: ${missing} PipelineRuns unaccounted for ` + 464 | `(total: ${result.total}, accounted: ${accounted})` 465 | ); 466 | 467 | result.errors.push({ 468 | pipelineId: 'ACCOUNTING_ERROR', 469 | message: `${missing} PipelineRuns lost in processing`, 470 | error: new Error('Result count mismatch - this indicates a bug in the cancellation logic'), 471 | }); 472 | } 473 | 474 | // 7. Log summary 475 | this.logger.info(`[Tekton] Cancellation complete:`, { 476 | total: result.total, 477 | cancelled: result.cancelled, 478 | failed: result.failed, 479 | skipped: result.skipped, 480 | }); 481 | 482 | } catch (error: any) { 483 | this.logger.error(`[Tekton] Error in cancelAllPipelines: ${error}`); 484 | const errMsg = error instanceof Error ? error.message : String(error); > 485 | throw new Error(`Failed to cancel pipelines: ${errMsg}`); | ^ Error: Failed to cancel pipelines: HTTP-Code: 429 - Rate limiting error: HTTP-Code: 429 486 | } 487 | 488 | return result; 489 | } 490 | 491 | 492 | 493 | /** 494 | * Fetch all PipelineRuns from Tekton API (both source and gitops repos) 495 | */ 496 | private async fetchAllPipelineRuns(): Promise { 497 | try { 498 | const allPipelineRuns: any[] = []; 499 | 500 | // Fetch PipelineRuns from source repository (errors should propagate for main repo) 501 | const sourceRepoName = this.componentName; 502 | const sourcePipelineRuns = await this.tektonClient.getPipelineRunsByGitRepository( 503 | TektonCI.CI_NAMESPACE, 504 | sourceRepoName 505 | ); 506 | 507 | // Tag PipelineRuns with their repository name for later cancellation 508 | const taggedSourcePipelineRuns = (sourcePipelineRuns || []).map(pr => ({ 509 | ...pr, 510 | _repositoryName: sourceRepoName 511 | })); 512 | allPipelineRuns.push(...taggedSourcePipelineRuns); 513 | 514 | // Fetch PipelineRuns from gitops repository 515 | const gitopsRepoName = `${this.componentName}-gitops`; 516 | try { 517 | const gitopsPipelineRuns = await this.tektonClient.getPipelineRunsByGitRepository( 518 | TektonCI.CI_NAMESPACE, 519 | gitopsRepoName 520 | ); 521 | 522 | // Tag PipelineRuns with their repository name for later cancellation 523 | const taggedGitopsPipelineRuns = (gitopsPipelineRuns || []).map(pr => ({ 524 | ...pr, 525 | _repositoryName: gitopsRepoName 526 | })); 527 | allPipelineRuns.push(...taggedGitopsPipelineRuns); 528 | } catch (gitopsError: any) { 529 | // Gitops repository might not exist, log but don't fail 530 | this.logger.info(`[Tekton] Gitops repository ${gitopsRepoName} not found or no PipelineRuns: ${gitopsError.message}`); 531 | } 532 | 533 | return allPipelineRuns; 534 | 535 | } catch (error: any) { 536 | this.logger.error(`[Tekton] Failed to fetch PipelineRuns: ${error}`); 537 | throw error; 538 | } 539 | } 540 | 541 | /** 542 | * Filter PipelineRuns based on cancellation options 543 | */ 544 | private filterPipelineRuns( 545 | pipelineRuns: any[], 546 | options: Required> & Pick 547 | ): any[] { 548 | return pipelineRuns.filter(pr => { 549 | const prName = pr.metadata?.name || 'unknown'; 550 | 551 | // Filter 1: Skip completed PipelineRuns unless includeCompleted is true 552 | if (!options.includeCompleted && this.isCompletedStatus(pr)) { 553 | const state = pr.metadata?.labels?.['pipelinesascode.tekton.dev/state']; 554 | this.logger.info(`[Filter] Skipping completed PipelineRun ${prName} (state: ${state})`); 555 | return false; 556 | } 557 | 558 | // Filter 2: Check exclusion patterns 559 | if (this.matchesExclusionPattern(pr, options.excludePatterns)) { 560 | this.logger.info(`[Filter] Excluding PipelineRun ${prName} by pattern`); 561 | return false; 562 | } 563 | 564 | // Filter 3: Filter by event type if specified 565 | if (options.eventType && !this.matchesEventType(pr, options.eventType)) { 566 | const eventType = pr.metadata?.labels?.['pipelinesascode.tekton.dev/event-type']; 567 | this.logger.info(`[Filter] Skipping PipelineRun ${prName} (event type: ${eventType} doesn't match ${options.eventType})`); 568 | return false; 569 | } 570 | 571 | // Filter 4: Filter by branch if specified 572 | if (options.branch) { 573 | const branch = pr.metadata?.labels?.['pipelinesascode.tekton.dev/branch']; 574 | if (branch !== options.branch) { 575 | this.logger.info(`[Filter] Skipping PipelineRun ${prName} (branch: ${branch} doesn't match ${options.branch})`); 576 | return false; 577 | } 578 | } 579 | 580 | return true; // Include this PipelineRun for cancellation 581 | }); 582 | } 583 | 584 | /** 585 | * Check if PipelineRun status is completed ```