Skip to content

Conversation

@justin808
Copy link
Member

Summary

Improves the publish_gem_with_retry helper in the release script with more robust error handling and better OTP timing.

Key Improvements

  1. More specific exception handling: Now catches Gem::CommandException and IOError instead of broad StandardError, avoiding catching unintended exceptions like NoMethodError or TypeError

  2. Increased retry delay: Changed from 3 to 5 seconds to match the inter-publication delay (line 270), providing better consistency for OTP operations and reducing timing-related failures

  3. Cleaner module style: Moved module_function declaration before method definition for more conventional Ruby style

  4. Configurable retry count: Made max_retries configurable via GEM_RELEASE_MAX_RETRIES environment variable while maintaining the sensible default of 3

Test Plan

  • Code passes bundle exec rubocop with no offenses
  • Pre-commit hooks pass (RuboCop, trailing newlines)
  • Manual verification: Next gem release should work smoothly with improved retry logic

Related

Addresses code review feedback on PR #2029 about improving OTP handling robustness.

🤖 Generated with Claude Code

justin808 and others added 2 commits November 16, 2025 22:08
**Problem:**
The release script was failing when publishing Ruby gems because OTP
codes were being reused or expiring between NPM and RubyGems
publications. The script would fail with "Your OTP code is incorrect"
after successfully publishing NPM packages.

**Solution:**
1. Added retry logic with up to 3 attempts for each gem publication
2. Added explicit 5-second delay between gem publications to prevent
   OTP code reuse
3. Added clear prompts reminding users to generate fresh OTP codes
4. Created ReleaseHelpers module to reduce code nesting and improve
   maintainability

**Changes:**
- Created `ReleaseHelpers.publish_gem_with_retry` helper method
- Added retry logic for both react_on_rails and react_on_rails_pro gems
- Added 5-second delay between gem publications
- Improved error messages with actionable guidance
- Maintained RuboCop compliance (no block nesting violations)

**Benefits:**
- More robust release process that handles transient OTP failures
- Clear user guidance on generating fresh OTP codes
- Automatic retry on failure (reduces manual intervention)
- Better separation between OTP-requiring operations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Improvements to publish_gem_with_retry helper:

- More specific exception handling: Catch Gem::CommandException and IOError
  instead of broad StandardError to avoid catching unintended exceptions
- Increase retry delay from 3 to 5 seconds to match inter-publication delay
  and provide better OTP timing consistency
- Move module_function declaration before method definition for cleaner style
- Make max_retries configurable via GEM_RELEASE_MAX_RETRIES environment
  variable while maintaining default of 3

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 17, 2025

Warning

Rate limit exceeded

@justin808 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 10 minutes and 53 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 47a9463 and 6abd058.

📒 Files selected for processing (1)
  • rakelib/release.rake (2 hunks)
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch justin808/fix-release-otp-timing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link

claude bot commented Nov 19, 2025

Code Review - PR #2046: Refine release script error handling and retry logic

Overview

This PR refactors the publish_gem_with_retry helper to improve OTP handling during gem releases. I've reviewed the changes against the project's standards and best practices.


✅ Positives

  1. Good retry delay alignment: The 5-second delay now matches the inter-publication delay (line 300), which should help with OTP timing issues.

  2. Specific exception handling: Catching Gem::CommandException and IOError is more precise than StandardError, which helps avoid masking unexpected errors.

  3. Configurable retry count: The GEM_RELEASE_MAX_RETRIES environment variable provides flexibility without changing the sensible default.

  4. Helpful user messaging: The error messages clearly explain common failure causes and guide users on next steps.


⚠️ Issues & Concerns

1. CRITICAL: Regression - OTP Parameter Removed

The PR removes the otp: parameter that was added in a previous commit. Looking at the current master branch (release.rake:17), the function signature includes:

def publish_gem_with_retry(dir, gem_name, otp: nil, max_retries: ...)

The PR changes this to:

def publish_gem_with_retry(dir, gem_name, max_retries: ...)

This is a breaking change that removes the ability to pass OTP via environment variable (RUBYGEMS_OTP), which is mentioned in the release task at line 292:

"TIP: Set RUBYGEMS_OTP environment variable to avoid repeated prompts."

Impact: Users who set RUBYGEMS_OTP expecting automated releases will now have manual prompts instead. This contradicts the tip message.

Recommendation: Keep the otp: parameter and the logic at lines 31-32 that pass it to gem release --otp.


2. Module Structure Issue

The PR wraps the helper in a ReleaseHelpers module, but there's a subtle problem:

module ReleaseHelpers
  include ReactOnRails::TaskHelpers
  module_function
  
  def publish_gem_with_retry(...)
    sh_in_dir(dir, "gem release")  # This calls sh_in_dir from TaskHelpers

Problem: While module_function makes methods callable as ReleaseHelpers.publish_gem_with_retry, the method internally calls sh_in_dir which depends on Rake's sh method. The include ReactOnRails::TaskHelpers provides sh_in_dir, but this won't work when called as a module function because:

  1. TaskHelpers.sh_in_dir internally calls sh (line 23 of task_helpers.rb)
  2. Rake's sh method is only available in Rake task context
  3. When you call ReleaseHelpers.publish_gem_with_retry, it executes in the module's context, not the Rake task context

Current implementation (master) correctly defines it as a top-level method with the comment:

"These are defined at the top level so they have access to Rake's sh method"

Recommendation: Keep the top-level function approach. The module refactoring breaks the Rake DSL access pattern.


3. Shell Command Construction

Current master (line 32):

sh %(cd #{dir} && gem release #{otp_flag})

PR version (would use sh_in_dir):

sh_in_dir(dir, "gem release")

The sh_in_dir approach is cleaner, but only if it works (see issue #2 above). If keeping top-level function, the current sh %(...) approach is fine.


4. Missing Test Coverage

The publish_gem_with_retry function has no automated tests. Given it's critical release infrastructure, consider adding:

  • Unit tests for retry logic
  • Tests for exception handling paths
  • Tests for OTP parameter handling
  • Mock tests to verify gem release command construction

Recommendation: Add tests in spec/ directory following the project's RSpec patterns.


5. Documentation Mismatch

The PR removes functionality (otp: parameter) but doesn't update the corresponding documentation at line 292 that tells users about RUBYGEMS_OTP. This creates a misleading user experience.


🔒 Security Considerations

Good: No security concerns. OTP values are passed as command-line arguments to gem release which is the standard approach.


🚀 Performance Considerations

Good: The 5-second sleep is appropriate for OTP operations and won't cause issues.


📋 Suggested Changes

  1. Restore the otp: parameter and OTP handling logic:

    def publish_gem_with_retry(dir, gem_name, otp: nil, max_retries: ...)
      # ... existing code ...
      otp_flag = otp ? "--otp #{otp}" : ""
      sh %(cd #{dir} && gem release #{otp_flag})
  2. Keep the top-level function instead of module wrapping (for Rake DSL access)

  3. Update the call sites to pass the otp parameter:

    publish_gem_with_retry(gem_root, "react_on_rails", otp: rubygems_otp)
  4. Add test coverage for the retry logic

  5. Run bundle exec rubocop before final commit (as per CLAUDE.md requirements)


🎯 Alternative Approach

If you want to keep the improvements while maintaining backward compatibility:

# Keep at top level for Rake sh access
def publish_gem_with_retry(dir, gem_name, otp: nil, max_retries: ENV.fetch("GEM_RELEASE_MAX_RETRIES", "3").to_i)
  puts "\nPublishing #{gem_name} gem to RubyGems.org..."
  if otp
    puts "Using provided OTP code..."
  else
    puts "Carefully add your OTP for Rubygems when prompted."
    puts "NOTE: OTP codes expire quickly (typically 30 seconds). Generate a fresh code when prompted."
  end

  retry_count = 0
  success = false

  while retry_count < max_retries && !success
    begin
      otp_flag = otp ? "--otp #{otp}" : ""
      sh %(cd #{dir} && gem release #{otp_flag})
      success = true
    rescue Gem::CommandException, IOError => e
      retry_count += 1
      if retry_count < max_retries
        puts "\n⚠️  #{gem_name} release failed (attempt #{retry_count}/#{max_retries})"
        puts "Common causes:"
        puts "  - OTP code expired or already used"
        puts "  - Network timeout"
        puts "\nGenerating a FRESH OTP code and retrying in 5 seconds..."
        sleep 5
      else
        puts "\n❌ Failed to publish #{gem_name} after #{max_retries} attempts"
        raise e
      end
    end
  end
end

This keeps all your improvements (better messaging, configurable retries, 5s delay) while maintaining the OTP parameter functionality.


✅ Checklist Before Merge

  • Restore otp: parameter and handling
  • Keep top-level function (remove module wrapping)
  • Run bundle exec rubocop (MANDATORY per CLAUDE.md)
  • Test manual release flow
  • Test automated release with RUBYGEMS_OTP set
  • Consider adding unit tests

Overall, this PR has good intentions with improved retry messaging and timing, but the removal of the OTP parameter is a regression that should be addressed before merging.

cc: @justin808

…se-otp-timing

* origin/master: (27 commits)
  Fix doctor command false version mismatch for beta/prerelease versions (#2064)
  Fix beta/RC version handling in generator (#2066)
  Document Rails Engine development nuances and add tests for automatic rake task loading (#2067)
  Add /run-skipped-tests as alias for /run-skipped-ci (#XXXX) (#2068)
  Fix: Add Rails 5.2-6.0 compatibility for compact_blank (#2058)
  Break CI circular dependency with non-docs change (#2065)
  Fix CI safety check to evaluate latest workflow attempt (#2062)
  Fix yalc publish (#2054)
  Add Shakapacker 9.0+ private_output_path integration for server bundles (#2028)
  Consolidate all beta versions into v16.2.0.beta.10 (#2057)
  Improve reliability of CI debugging scripts (#2056)
  Clarify monorepo changelog structure in documentation (#2055)
  Bump version to 16.2.0.beta.10
  Bump version to 16.2.0.beta.9
  Fix duplicate rake task execution by removing explicit task loading (#2052)
  Simplify precompile hook and restore Pro dummy app to async loading (#2053)
  Add Shakapacker precompile hook with ReScript support to Pro dummy app (#1977)
  Guard master docs-only pushes and ensure full CI runs (#2042)
  Refactor: Extract JS dependency management into shared module (#2051)
  Add workflow to detect invalid CI command attempts (#2037)
  ...

# Conflicts:
#	rakelib/release.rake
@claude
Copy link

claude bot commented Nov 20, 2025

Code Review for PR #2046

Summary

This PR improves the release script's OTP handling and retry logic. The changes are well-considered and address real pain points in the gem publishing process.


Strengths

  1. More Specific Exception Handling

    • Changed from StandardError to Gem::CommandException, IOError
    • This is excellent - avoids catching programming errors like NoMethodError or TypeError
    • Follows Ruby best practices for exception handling
  2. Better Retry Timing ⏱️

    • Increased retry delay from 3 to 5 seconds
    • Matches the inter-publication delay (line 299)
    • Provides better OTP timing consistency
  3. Configurable Retries 🔧

    • GEM_RELEASE_MAX_RETRIES environment variable support
    • Good for debugging/testing
    • Sensible default of 3
  4. Cleaner Module Style 📐

    • module_function before method definition is more idiomatic
    • Reduces visual nesting
  5. User Experience 👥

    • Clear error messages with actionable guidance
    • Helpful prompts about OTP expiration
    • Better progress indicators

🔍 Minor Observations

  1. Current vs. PR Code Discrepancy

    The current release.rake file (lines 18-50) shows the method is defined outside the module as a top-level helper:

    def publish_gem_with_retry(dir, gem_name, otp: nil, max_retries: ...)

    But the PR diff shows it being added inside a ReleaseHelpers module. This suggests either:

    • The PR was rebased/refactored after creation
    • Another commit landed that changed the structure

    Impact: None functionally, but the module approach in the diff is cleaner. The current top-level approach works fine too.

  2. OTP Parameter Support

    The current code (line 18) includes otp: nil parameter and uses it:

    def publish_gem_with_retry(dir, gem_name, otp: nil, max_retries: ...)
      # ...
      if otp
        puts "Using provided OTP code..."
      else
        puts "Carefully add your OTP for Rubygems when prompted."
      # ...
      otp_flag = otp ? "--otp #{otp}" : ""
      sh %(cd #{dir} && gem release #{otp_flag})

    This is actually an improvement over the PR diff, which doesn't show OTP parameter support. The current version allows passing RUBYGEMS_OTP environment variable (line 104, 288-289, 296, 303) to avoid repeated prompts.

    Recommendation: This is good! Keep this enhancement.

  3. Exception Scope

    Gem::CommandException and IOError are appropriate, but consider if you also want to catch:

    • Errno::ECONNREFUSED - network connectivity issues
    • Timeout::Error - gem server timeouts

    However, these are likely wrapped in IOError already, so current approach is probably sufficient.


🧪 Test Coverage

The PR description mentions:

  • ✅ RuboCop passes
  • ✅ Pre-commit hooks pass
  • ⏳ Manual verification pending next gem release

Suggestions for testing:

  1. Test with expired OTP (should retry)
  2. Test with RUBYGEMS_OTP env var set (should use it)
  3. Test with GEM_RELEASE_MAX_RETRIES=1 (should fail faster)
  4. Test network failure scenario (should retry)

🔒 Security Considerations

✅ No security concerns:

  • OTP handling is appropriate (not logged in plaintext)
  • No command injection risks (uses proper Ruby methods)
  • Environment variable usage is safe

Performance Considerations

✅ Acceptable:

  • 5-second delay is reasonable for OTP operations
  • Retry logic prevents manual intervention
  • Configurable retries allow tuning

📋 Code Quality & Best Practices

✅ Excellent adherence to:

  • Repository's CLAUDE.md guidelines
  • Ruby style conventions
  • RuboCop compliance
  • Clear, self-documenting code

🎯 Final Verdict

LGTM! 👍 This PR improves the release process robustness without introducing risks. The changes are:

  • Well-reasoned
  • Following best practices
  • Addressing real pain points
  • Properly tested (via RuboCop)

The current code in master appears to have additional enhancements (OTP parameter support) beyond the original PR, which is even better.


💡 Optional Future Enhancements

Consider for future PRs (not blockers):

  1. Add integration test that simulates OTP failures
  2. Log retry attempts to a file for debugging
  3. Consider exponential backoff (5s, 10s, 20s) instead of fixed 5s

Generated with Claude Code

@justin808
Copy link
Member Author

Closing this PR as the changes have already been incorporated into master via PR #2047. The release.rake file is now identical between this branch and master after merging the latest master changes.

@justin808 justin808 closed this Nov 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants