Skip to content

Fix: Implement reset time parsing for auto-continue when limit is reached #19 - #35

Merged
leonvanzyl merged 3 commits into
AutoForgeAI:masterfrom
coreycauble:master
Jan 10, 2026
Merged

leonvanzyl merged 3 commits into
AutoForgeAI:masterfrom
coreycauble:master

Conversation

@coreycauble

@coreycauble coreycauble commented Jan 9, 2026 •

Copy link
Copy Markdown
Contributor

Added functionality to parse and handle reset time for auto-continue based on agent response when Limit is reached for Claude Code SDK

Summary by CodeRabbit

  • New Features

    • Enhanced auto-retry: detects reported reset times in responses, computes timezone-aware retry delays (with human-friendly target-time notices), and waits dynamically before continuing.
  • Bug Fixes

    • Improved console output on Windows for more reliable message display and encoding.

✏️ Tip: You can customize this high-level summary in your review settings.

Added functionality to parse and handle reset time for auto-continue based on agent response when Limit is reached for Claude Code SDK
@coderabbitai

coderabbitai Bot commented Jan 9, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The agent now parses timezone-aware reset times from "limit reached" responses and replaces the fixed continuation delay with a computed, time-zone-aware sleep duration derived from the parsed target time; falls back to the default delay on parse failure.

Changes

Cohort / File(s) Summary
Reset Time Parsing & Timezone-Aware Delays
agent.py
Added imports (re, datetime, timedelta, ZoneInfo). On "continue" status, detect "limit reached" text, parse reset time and timezone via regex, normalize 12‑hour times, compute target datetime in parsed timezone (roll to next day if needed), clamp delay ≤24h, and sleep for computed delay_seconds. Print detailed "Code Limit Reached" message with target time or generic auto‑continue message; flush stdout before sleeping. Also adjusted Windows console encoding string usage and preserved fallback to default delay on parse/resolve failure.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I parse the clock where the messages chime,
AM or PM, I hunt the time.
Zones and regex lead the way,
I nap until the break of day.
Then nibble code and hop away.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly describes the main change: implementing reset time parsing for auto-continue when a usage limit is reached, which matches the core functionality added in agent.py.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @agent.py:
- Around line 19-23: The Windows encoding wrapper currently assumes sys.stdout
and sys.stderr expose a .buffer attribute and will raise if they don’t; update
the conditional in the sys.platform == "win32" block to first verify
hasattr(sys.stdout, "buffer") and hasattr(sys.stderr, "buffer") (or wrap the
TextIOWrapper creation in a try/except AttributeError) and only replace streams
when the buffer attribute exists, otherwise skip the rewrapping to avoid
import-time failures in environments/IDEs that replace sys.stdout/sys.stderr.
- Around line 200-255: The limit-reset parsing is brittle and can produce
arbitrarily long sleeps; update the check on the agent response to detect "limit
reached" more flexibly (e.g., case-insensitive word-boundary search rather than
response.lower().strip().startswith(...)), replace the regex with a more
tolerant pattern like
(?i)\bresets(?:\s+at)?\s+(\d+)(?::(\d+))?\s*(am|pm)\s*\(([^)]+)\) to capture
common variants, strip whitespace from the captured tz_name before calling
ZoneInfo (tz_name = match.group(4).strip()), and after computing delay_seconds
clamp it into a safe range (e.g., delay_seconds = max(0, min(delay_seconds,
24*3600))) so print_progress_summary, AUTO_CONTINUE_DELAY_SECONDS and the
asyncio.sleep call use a bounded delay.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a195d6d and 7f436a4.

📒 Files selected for processing (1)
  • agent.py
🧰 Additional context used
🧬 Code graph analysis (1)
agent.py (1)
progress.py (1)
  • print_progress_summary (211-223)

Comment thread agent.py
Comment on lines 19 to +23
# Fix Windows console encoding for Unicode characters (emoji, etc.)
# Without this, print() crashes when Claude outputs emoji like ✅
if sys.platform == "win32":
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard Win32 encoding wrapper for environments without .buffer.

Some runners/IDEs replace sys.stdout/sys.stderr with objects that may not have .buffer; this would raise on Windows at import time.

Proposed fix (defensive guard)
 if sys.platform == "win32":
-    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
-    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
+    if hasattr(sys.stdout, "buffer"):
+        sys.stdout = io.TextIOWrapper(
+            sys.stdout.buffer, encoding="utf-8", errors="replace"
+        )
+    if hasattr(sys.stderr, "buffer"):
+        sys.stderr = io.TextIOWrapper(
+            sys.stderr.buffer, encoding="utf-8", errors="replace"
+        )
🤖 Prompt for AI Agents
In @agent.py around lines 19 - 23, The Windows encoding wrapper currently
assumes sys.stdout and sys.stderr expose a .buffer attribute and will raise if
they don’t; update the conditional in the sys.platform == "win32" block to first
verify hasattr(sys.stdout, "buffer") and hasattr(sys.stderr, "buffer") (or wrap
the TextIOWrapper creation in a try/except AttributeError) and only replace
streams when the buffer attribute exists, otherwise skip the rewrapping to avoid
import-time failures in environments/IDEs that replace sys.stdout/sys.stderr.

Comment thread agent.py Resolved
Applied Fixes
More flexible string matching: Changed from response.lower().strip().startswith("limit reached") to "limit reached" in response.lower() to handle cases where the message has prefix text or variations in whitespace.

Improved regex pattern: Updated to r"(?i)\bresets(?:\s+at)?\s+(\d+)(?::(\d+))?\s*(am|pm)\s*\(([^)]+)\)" which now handles:

Optional "at" after "resets" (e.g., "resets at 3pm" or "resets 3pm")
Flexible whitespace around components
Word boundaries to prevent partial matches
Timezone sanitization: Added .strip() to tz_name = match.group(4).strip() to remove any leading/trailing whitespace that could cause ZoneInfo() to fail.

Safety clamp: Added delay_seconds = min(delta.total_seconds(), 24 * 60 * 60) to ensure the delay never exceeds 24 hours, preventing the agent from being stuck waiting for extremely long periods.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @agent.py:
- Around line 208-223: The regex and conversion allow invalid 12-hour values;
after matching in the block that sets hour/minute/period/tz_name (the match
handling where you convert to 24-hour format), validate that hour is between 1
and 12 and minute is between 0 and 59 (or tighten the regex to enforce those
ranges) and if the values are invalid, handle gracefully (e.g., skip parsing,
log/warn, or return None) before doing the 24-hour conversion or calling
datetime.replace to avoid producing invalid datetimes.
- Line 15: The module-level import of ZoneInfo in agent.py will raise
ImportError on Python <3.9; either protect it by wrapping the import in a
try/except and fall back to the backports.zoneinfo package or a
pytz/dateutil-based replacement (setting a ZoneInfo alias used throughout the
module), or instead declare and enforce Python>=3.9 in
pyproject.toml/setup.py/README so the import is guaranteed to succeed; update
imports and any uses of ZoneInfo accordingly to avoid import-time crashes.
🧹 Nitpick comments (3)
agent.py (3)

240-241: Consider improving error logging for debugging.

The generic error message doesn't include details about what was being parsed. Consider logging the matched values or the original response excerpt to aid debugging when reset time parsing fails.

💡 Suggested enhancement
                    except Exception as e:
-                       print(f"Error parsing reset time: {e}, using default delay")
+                       print(f"Error parsing reset time (hour={hour}, minute={minute}, tz={tz_name}): {e}, using default delay")

243-255: Consider adding an upper bound check for delay_seconds.

While the parsed reset time logic handles date rollover correctly, there's no upper bound on delay_seconds. If the reset time is far in the future (e.g., 23+ hours), the agent will sleep for an extended period. Consider whether a maximum delay (e.g., 24 hours) should be enforced to prevent unexpectedly long waits.

⏱️ Suggested safeguard
                        delta = target - now
                        delay_seconds = delta.total_seconds()
+                       
+                       # Cap delay at 24 hours to avoid excessive waiting
+                       MAX_DELAY_SECONDS = 24 * 60 * 60  # 24 hours
+                       if delay_seconds > MAX_DELAY_SECONDS:
+                           print(f"Computed delay ({delay_seconds:.0f}s) exceeds maximum, capping at {MAX_DELAY_SECONDS}s")
+                           delay_seconds = MAX_DELAY_SECONDS
+                       
                        target_time_str = target.strftime("%B %d, %Y at %I:%M %p %Z")

Alternatively, if extended waits are intentional, this suggestion can be ignored.


253-253: Optional: Remove redundant flush.

Since the print statements above (lines 244-251) already use flush=True, the explicit sys.stdout.flush() call on line 253 is redundant. While harmless, you could remove it for cleaner code.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f436a4 and 2b2e28a.

📒 Files selected for processing (1)
  • agent.py
🧰 Additional context used
🧬 Code graph analysis (1)
agent.py (1)
progress.py (1)
  • print_progress_summary (211-223)
🔇 Additional comments (1)
agent.py (1)

201-202: LGTM!

Good defensive initialization—ensures both variables have safe defaults before the conditional parsing logic.

Comment thread agent.py
from datetime import datetime, timedelta
from pathlib import Path
from typing import Optional
from zoneinfo import ZoneInfo

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check Python version requirements in project config files
echo "=== Checking for Python version specifications ==="

# Search for version specs in common project config files
fd -type f \( -name "setup.py" -o -name "setup.cfg" -o -name "pyproject.toml" -o -name "requirements*.txt" -o -name ".python-version" \) -exec cat {} \;

echo ""
echo "=== Checking for version checks in codebase ==="
# Look for sys.version_info checks
rg "sys\.version_info|python_requires" -A 3 -B 1

echo ""
echo "=== Checking for ZoneInfo usage and fallbacks ==="
# Check for ZoneInfo imports and any try/except handling
rg "zoneinfo|ZoneInfo" -B 2 -A 2

echo ""
echo "=== Checking for pytz or dateutil imports (fallback mechanisms) ==="
rg "pytz|dateutil\.tz" -B 1 -A 1

Repository: leonvanzyl/autocoder

Length of output: 859


🏁 Script executed:

#!/bin/bash
# List all files in repo root to see what config exists
echo "=== Files in repository root ==="
ls -la

echo ""
echo "=== Context around ZoneInfo usage in agent.py ==="
rg -B 5 -A 10 "tz = ZoneInfo"

echo ""
echo "=== Check README or docs for version info ==="
find . -maxdepth 2 -type f \( -iname "README*" -o -iname "*.md" \) -exec grep -l -i "python" {} \;

Repository: leonvanzyl/autocoder

Length of output: 150


🏁 Script executed:

#!/bin/bash
# Read agent.py directly to see context around ZoneInfo
echo "=== agent.py lines 1-30 ==="
head -30 agent.py

echo ""
echo "=== agent.py lines 195-260 (around ZoneInfo usage) ==="
sed -n '195,260p' agent.py

Repository: leonvanzyl/autocoder

Length of output: 3748


Protect ZoneInfo import for Python < 3.9 compatibility or document minimum version requirement.

ZoneInfo (line 15) requires Python 3.9+, but this minimum version is not documented in project config, and the import is unprotected at module level. The code will immediately fail to import on Python 3.8 or earlier. Either add a version check/fallback (e.g., using pytz or dateutil.tz for older versions) or explicitly document Python 3.9+ as the minimum requirement in setup.py/pyproject.toml/README.

🤖 Prompt for AI Agents
In @agent.py at line 15, The module-level import of ZoneInfo in agent.py will
raise ImportError on Python <3.9; either protect it by wrapping the import in a
try/except and fall back to the backports.zoneinfo package or a
pytz/dateutil-based replacement (setting a ZoneInfo alias used throughout the
module), or instead declare and enforce Python>=3.9 in
pyproject.toml/setup.py/README so the import is guaranteed to succeed; update
imports and any uses of ZoneInfo accordingly to avoid import-time crashes.

Comment thread agent.py
Comment on lines +208 to +223
match = re.search(
r"resets (\d+)(?::(\d+))?(am|pm) \(([^)]+)\)",
response,
re.IGNORECASE,
)
if match:
hour = int(match.group(1))
minute = int(match.group(2)) if match.group(2) else 0
period = match.group(3).lower()
tz_name = match.group(4)

# Convert to 24-hour format
if period == "pm" and hour != 12:
hour += 12
elif period == "am" and hour == 12:
hour = 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate hour range to prevent invalid time conversions.

The regex \d+ captures any number for the hour, but valid 12-hour format hours are 1-12. If the agent returns an invalid hour (e.g., "resets 25pm"), the conversion logic will produce incorrect values (hour=37 in this case), which may cause datetime.replace() to fail or produce unexpected results.

🛡️ Proposed fix: Add hour validation
                if match:
                    hour = int(match.group(1))
                    minute = int(match.group(2)) if match.group(2) else 0
                    period = match.group(3).lower()
                    tz_name = match.group(4)
+
+                   # Validate hour is in valid 12-hour range
+                   if not (1 <= hour <= 12):
+                       print(f"Invalid hour {hour} in reset time, using default delay")
+                       continue  # Skip to the else block (default delay)

                    # Convert to 24-hour format
                    if period == "pm" and hour != 12:
                        hour += 12
                    elif period == "am" and hour == 12:
                        hour = 0

Alternatively, you could tighten the regex pattern itself:

-                   r"resets (\d+)(?::(\d+))?(am|pm) \(([^)]+)\)",
+                   r"resets (1[0-2]|[1-9])(?::([0-5][0-9]))?(am|pm) \(([^)]+)\)",

This pattern ensures hour is 1-12 and minute (if present) is 00-59.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @agent.py around lines 208 - 223, The regex and conversion allow invalid
12-hour values; after matching in the block that sets hour/minute/period/tz_name
(the match handling where you convert to 24-hour format), validate that hour is
between 1 and 12 and minute is between 0 and 59 (or tighten the regex to enforce
those ranges) and if the values are invalid, handle gracefully (e.g., skip
parsing, log/warn, or return None) before doing the 24-hour conversion or
calling datetime.replace to avoid producing invalid datetimes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
agent.py (2)

236-238: Consider extracting the 24-hour limit as a named constant.

The magic number 24 * 60 * 60 (86400 seconds) is used for clamping the delay. Extracting this to a module-level constant would improve readability.

♻️ Proposed refactor

At the module level (near line 35 with other constants):

 # Configuration
 AUTO_CONTINUE_DELAY_SECONDS = 3
+MAX_DELAY_SECONDS = 24 * 60 * 60  # 24 hours

Then update the usage:

-                        delay_seconds = min(
-                            delta.total_seconds(), 24 * 60 * 60
-                        )  # Clamp to 24 hours max
+                        delay_seconds = min(delta.total_seconds(), MAX_DELAY_SECONDS)

224-242: Consider refactoring parse logic into a separate function.

The reset time parsing logic (lines 208-242) could be extracted into a dedicated function for improved testability and maintainability. This would allow unit testing of timezone conversion, 12-hour format handling, and edge cases independently.

♻️ Example refactor
def parse_reset_time(response: str) -> Optional[tuple[float, str]]:
    """
    Parse reset time from agent response.
    
    Returns:
        (delay_seconds, target_time_str) if parsed successfully, None otherwise
    """
    match = re.search(
        r"(?i)\bresets(?:\s+at)?\s+(\d+)(?::(\d+))?\s*(am|pm)\s*\(([^)]+)\)",
        response,
    )
    if not match:
        return None
        
    hour = int(match.group(1))
    minute = int(match.group(2)) if match.group(2) else 0
    period = match.group(3).lower()
    tz_name = match.group(4).strip()
    
    # Convert to 24-hour format
    if period == "pm" and hour != 12:
        hour += 12
    elif period == "am" and hour == 12:
        hour = 0
    
    try:
        tz = ZoneInfo(tz_name)
        now = datetime.now(tz)
        target = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
        
        if target <= now:
            target += timedelta(days=1)
        
        delta = target - now
        delay_seconds = min(delta.total_seconds(), 24 * 60 * 60)
        target_time_str = target.strftime("%B %d, %Y at %I:%M %p %Z")
        
        return (delay_seconds, target_time_str)
    except Exception as e:
        print(f"Error parsing reset time: {e}")
        return None

Then in the main flow:

if "limit reached" in response.lower():
    print("Claude Agent SDK indicated limit reached.")
    
    parsed = parse_reset_time(response)
    if parsed:
        delay_seconds, target_time_str = parsed
    # else: use defaults already set
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b2e28a and 9c07dd7.

📒 Files selected for processing (1)
  • agent.py
🔇 Additional comments (5)
agent.py (5)

201-205: LGTM! Flexible limit detection.

The case-insensitive substring check for "limit reached" is appropriately flexible, allowing the phrase to appear anywhere in the response rather than requiring a specific position or format.


208-216: Regex pattern effectively captures reset time components.

The pattern correctly handles:

  • Optional "at" keyword
  • Flexible whitespace
  • Optional minutes (defaulting to 0)
  • Case-insensitive matching
  • Timezone name extraction

219-222: 12-hour to 24-hour conversion logic is correct.

The conversion properly handles:

  • PM times: adds 12 except for 12 PM
  • AM times: converts 12 AM (midnight) to 0

244-256: Excellent user feedback and output handling.

The conditional messaging provides clear feedback to users about whether a specific reset time was detected or if the default delay is being used. The sys.stdout.flush() call ensures the pause message is displayed immediately before sleeping.


15-15: Python 3.11 is already required; Windows tzdata concerns are handled by existing error handling.

The project's pyproject.toml specifies python_version = "3.11", which exceeds the Python 3.9+ minimum required for ZoneInfo. Additionally, the code at line 225 already wraps ZoneInfo(tz_name) in a try-except block (lines 42-43) that gracefully catches any ZoneInfoNotFoundError on Windows systems lacking tzdata and falls back to the default auto-continue delay. No additional fallback imports, dependency declarations, or documentation changes are needed.

@leonvanzyl
leonvanzyl merged commit b633e6d into AutoForgeAI:master Jan 10, 2026
1 check passed
@leonvanzyl

Copy link
Copy Markdown
Collaborator

Thank you @coreycauble

pRizz added a commit to pRizz/autocoder that referenced this pull request Jan 21, 2026
- Add POST /api/providers endpoint for creating AI provider configurations
- Implement AES-256-GCM encryption for API key storage
- Add validation for required fields (id, name, apiKey)
- Return 201 status on successful creation
- Mask API key in response (show first 4 and last 4 chars)
- Add ValidationError handling with 400 status
- Export decryptApiKey for future use (provider connection testing)

Feature AutoForgeAI#35: Add provider via API - verified end-to-end

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
rudiheydra added a commit to rudiheydra/AutoBuildr that referenced this pull request Jan 27, 2026
- Add AcceptanceGate class with evaluate(run, acceptance_spec) method
- Implement gate_mode support (all_pass, any_pass)
- Add required validator flag enforcement
- Build acceptance_results array with per-validator outcomes
- Update AgentRun with final_verdict and acceptance_results
- Add GateResult dataclass for gate evaluation results
- Export AcceptanceGate and GateResult from api package
- Add comprehensive test suite (36 tests, all passing)
- Add verification script (11 steps, all passing)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CoreAspectStu pushed a commit to CoreAspectStu/autocoder-custom that referenced this pull request Feb 9, 2026
Fix: Implement reset time parsing for auto-continue when limit is reached AutoForgeAI#19
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