Skip to content

Conversation

@tallesborges
Copy link
Contributor

@tallesborges tallesborges commented Dec 15, 2025

User description

Summary

Adds platform:beam:retry-abandoned-batches command to recover beams whose batch transactions became invalid after a runtime/spec upgrade.

Problem

After a runtime upgrade, old transaction encoded data can fail validation and the daemon marks them as ABANDONED. These abandoned transactions never recover automatically, leaving beam claims stuck.

Solution

New artisan command that:

  • Finds batches with ABANDONED transactions for a given beam
  • Resets stuck claims (FAILED/IN_PROGRESS → PENDING)
  • Unlinks batch from the abandoned transaction
  • Allows BatchProcess to generate fresh transactions with current encoding

Usage

# Preview changes
php artisan platform:beam:retry-abandoned-batches <beam-code> --dry-run

# Apply changes
php artisan platform:beam:retry-abandoned-batches <beam-code>

After running, execute platform:process-beam-claims --test or wait for the worker to create new transactions.

Linear

https://linear.app/enjin/issue/PLA-2309


PR Type

Enhancement, Tests, Configuration


Description

  • Add command to retry abandoned batches

  • Reset failed/in-progress claims to pending

  • Register command in service provider

  • Add feature tests and test env tweaks


Diagram Walkthrough

flowchart LR
  SP["BeamServiceProvider registers commands"] -- includes --> CMD["RetryAbandonedBatches command"]
  CMD -- queries --> DB["Batches, Claims, Transactions"]
  CMD -- filters --> AB["Abandoned transactions only"]
  CMD -- updates --> CL["Claims -> PENDING"]
  CMD -- unlinks --> BB["Batches: clear transaction & processed_at"]
  TEST["Feature tests"] -- validate --> CMD
  CFG["phpunit.xml & testbench.yaml"] -- configure --> TEST
Loading

File Walkthrough

Relevant files
Enhancement
BeamServiceProvider.php
Register new retry-abandoned-batches command                         

src/BeamServiceProvider.php

  • Import RetryAbandonedBatches command.
  • Register command alongside BatchProcess.
+2/-1     
RetryAbandonedBatches.php
Implement abandoned batch recovery command                             

src/Commands/RetryAbandonedBatches.php

  • Add console command platform:beam:retry-abandoned-batches.
  • Filter eligible batches with abandoned transactions.
  • Reset FAILED/IN_PROGRESS claims to PENDING.
  • Unlink batch transaction_id and processed_at; support --dry-run.
+162/-0 
Tests
RetryAbandonedBatchesTest.php
Feature tests for retry-abandoned-batches command               

tests/Feature/Commands/RetryAbandonedBatchesTest.php

  • Add feature tests for recovery behavior.
  • Verify claim state resets and batch unlinking.
  • Test dry-run leaves data unchanged.
+81/-0   
Configuration changes
phpunit.xml
Bump PHPUnit memory limit                                                               

phpunit.xml

  • Increase memory limit to 512M for tests.
+1/-0     
testbench.yaml
Tune testbench environment settings                                           

testbench.yaml

  • Adjust DB host/port/name for tests.
  • Switch cache to array and queue to sync.
+5/-3     

Add platform:beam:retry-abandoned-batches command that resets failed
claims and batches whose transactions were abandoned after runtime
upgrades, allowing them to be reprocessed by BatchProcess.

Options:
- --dry-run: preview changes without writing
- --force: retry even if transaction is not abandoned
- --yes: skip confirmation prompt
@github-actions
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

Variables $updatedClaims and $updatedBatches are referenced by reference inside the transaction closure but are not initialized before use; in PHP this can emit notices or result in null when echoed. Consider initializing to 0 before the transaction.

DB::transaction(function () use (
    $claimsToResetQuery,
    $eligibleBatchIds,
    &$updatedClaims,
    &$updatedBatches,
): void {
    $updatedClaims = $claimsToResetQuery->update([
        'state' => ClaimStatus::PENDING->name,
    ]);

    $updatedBatches = BeamBatch::query()
        ->whereIn('id', $eligibleBatchIds)
        ->update([
            'processed_at' => null,
            'transaction_id' => null,
        ]);
});

$this->newLine();
$this->info('=== Results ===');
$this->info("Updated claims: {$updatedClaims}");
$this->info("Updated batches: {$updatedBatches}");
Query Efficiency

Iterates batches and performs a per-batch transaction lookup, causing N+1 queries. Fetch transactions for all candidate transaction_ids in a single query and map by id to reduce DB calls.

foreach ($batches as $batch) {
    $transaction = Transaction::query()
        ->where('id', $batch->transaction_id)
        ->first(['id', 'state', 'transaction_chain_hash']);

    if (!$transaction) {
        $this->line("  Skipping batch {$batch->id}: transaction not found.");

        continue;
    }

    if ($transaction->state !== TransactionState::ABANDONED->name) {
        $this->line("  Skipping batch {$batch->id}: transaction state is {$transaction->state}.");

        continue;
    }

    $eligibleBatches[] = $batch;
    $transactionIds[] = $transaction->id;
    $this->line(
        "  Eligible batch {$batch->id}: transaction {$transaction->id} (state: {$transaction->state})",
    );
}
Test Coverage Gap

No test asserts that claims in IN_PROGRESS state are reset to PENDING, though the command intends to handle both FAILED and IN_PROGRESS. Add a case ensuring IN_PROGRESS claims are included.

public function test_it_resets_abandoned_batches(): void
{
    $this->setupAbandonedBatch();

    BeamClaim::query()->update(['state' => ClaimStatus::FAILED->name]);

    $this->artisan('platform:beam:retry-abandoned-batches', [
        'code' => $this->beam->code,
    ])
        ->expectsOutputToContain('Updated claims:')
        ->expectsOutputToContain('Updated batches:')
        ->assertSuccessful();

    BeamClaim::all()->each(fn ($claim) => $this->assertEquals(ClaimStatus::PENDING->name, $claim->state));

    $this->batch->refresh();
    $this->assertNull($this->batch->processed_at);
    $this->assertNull($this->batch->transaction_id);
}

public function test_dry_run_does_not_modify_data(): void
{
    $this->setupAbandonedBatch();

    BeamClaim::query()->update(['state' => ClaimStatus::FAILED->name]);

    $this->artisan('platform:beam:retry-abandoned-batches', [
        'code' => $this->beam->code,
        '--dry-run' => true,
    ])
        ->expectsOutputToContain('Dry run enabled')
        ->assertSuccessful();

    $this->assertEquals(ClaimStatus::FAILED->name, BeamClaim::first()->state);
    $this->assertNotNull($this->batch->refresh()->transaction_id);

@github-actions
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Avoid resetting in-progress claims

Resetting claims still marked as IN_PROGRESS may race with active processing and
cause duplication. Exclude IN_PROGRESS from mass reset, limiting to FAILED claims,
or add a time-based guard if IN_PROGRESS is stale. This reduces risk of conflicting
updates.

src/Commands/RetryAbandonedBatches.php [114-118]

 $claimsToResetQuery = BeamClaim::query()
-            ->whereIn('beam_batch_id', $eligibleBatchIds)
-            ->whereNull('claimed_at')
-            ->whereIn('state', [ClaimStatus::FAILED->name, ClaimStatus::IN_PROGRESS->name]);
+    ->whereIn('beam_batch_id', $eligibleBatchIds)
+    ->whereNull('claimed_at')
+    ->whereIn('state', [ClaimStatus::FAILED->name]);
Suggestion importance[1-10]: 7

__

Why: Excluding IN_PROGRESS claims reduces the risk of racing with active processing, improving safety. While context-dependent, it is a reasonable guard against conflicting updates and aligns with the command's reset intent.

Medium
Initialize referenced counters safely

Avoid capturing by-reference variables that are not pre-initialized; if an exception
occurs before assignment, they remain undefined and later logging can error.
Initialize updatedClaims and updatedBatches before the transaction and remove by-ref
captures. This ensures consistent values even on failure.

src/Commands/RetryAbandonedBatches.php [137-153]

+$updatedClaims = 0;
+$updatedBatches = 0;
+
 DB::transaction(function () use (
-            $claimsToResetQuery,
-            $eligibleBatchIds,
-            &$updatedClaims,
-            &$updatedBatches,
-        ): void {
-            $updatedClaims = $claimsToResetQuery->update([
-                'state' => ClaimStatus::PENDING->name,
-            ]);
+    $claimsToResetQuery,
+    $eligibleBatchIds,
+    &$updatedClaims,
+    &$updatedBatches,
+): void {
+    $updatedClaims = $claimsToResetQuery->update([
+        'state' => ClaimStatus::PENDING->name,
+    ]);
 
-            $updatedBatches = BeamBatch::query()
-                ->whereIn('id', $eligibleBatchIds)
-                ->update([
-                    'processed_at' => null,
-                    'transaction_id' => null,
-                ]);
-        });
+    $updatedBatches = BeamBatch::query()
+        ->whereIn('id', $eligibleBatchIds)
+        ->update([
+            'processed_at' => null,
+            'transaction_id' => null,
+        ]);
+});
Suggestion importance[1-10]: 6

__

Why: Initializing $updatedClaims and $updatedBatches before the transaction avoids undefined variable usage if an exception occurs, improving robustness. The change is accurate and low risk, but it's a minor reliability improvement rather than a critical fix.

Low
General
Include in-progress abandoned batches

The subsequent logic only checks transaction state and does not use completed_at or
processed_at in eligibility; however requiring completed_at here could
unintentionally exclude abandoned in-progress batches. Remove the completed_at
filter to ensure all potentially abandoned batches are considered.

src/Commands/RetryAbandonedBatches.php [67-77]

 $batches = BeamBatch::query()
-            ->whereIn('id', $batchIds)
-            ->whereNotNull('completed_at')
-            ->whereNotNull('transaction_id')
-            ->get([
-                'id',
-                'transaction_id',
-                'processed_at',
-                'beam_type',
-                'collection_chain_id',
-            ]);
+    ->whereIn('id', $batchIds)
+    ->whereNotNull('transaction_id')
+    ->get([
+        'id',
+        'transaction_id',
+        'processed_at',
+        'beam_type',
+        'collection_chain_id',
+    ]);
Suggestion importance[1-10]: 4

__

Why: Removing the completed_at filter might broaden scope to include legitimately in-progress batches, which could be unintended; the current filter likely reflects business rules. The suggestion is plausible but speculative and could reduce correctness.

Low

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@pawell67
Copy link
Contributor

Can we have it as action in Nova?

- Use whereHas to filter abandoned transactions in SQL instead of PHP loop
- Remove unused Transaction import
- Remove verbose per-batch logging
- Cleaner and more efficient query flow
…s claims

Covers scenario where:
- Batch has completed_at, processed_at, and transaction_id set
- Transaction state is ABANDONED
- Claims are in FAILED or IN_PROGRESS state
- Verifies all claims reset to PENDING and batch cleared for reprocessing
@tallesborges
Copy link
Contributor Author

Can we have it as an action in Nova?

The Nova is only on the internal platform. We can implement it there, but we can also use the command runner.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants