Skip to content

Conversation

@ayuxsec
Copy link

@ayuxsec ayuxsec commented Jan 4, 2026

Partial Fix for #2345

Note: This PR only fixes how resume.cfg is written to disk. The core issue of how the resume.cfg is parsed is still present, I couldn’t track down the root cause in the limited time I had. I'll take another look when I get more free time. Thanks :D

before:

ayux@pop-os:~/Desktop/httpx/cmd/httpx$ echo -e "example.com\ntest.com\nfacebook.com\nhackerone.com\ngoogle.com\n" | go run .

    __    __  __       _  __
   / /_  / /_/ /_____ | |/ /
  / __ \/ __/ __/ __ \|   /
 / / / / /_/ /_/ /_/ /   |
/_/ /_/\__/\__/ .___/_/|_|
             /_/

                projectdiscovery.io

[INF] Current httpx version v1.7.4 (latest)
[WRN] UI Dashboard is disabled, Use -dashboard option to enable
https://example.com
https://google.com
^C[INF] CTRL+C pressed: Exiting
[INF] Creating resume file: resume.cfg
exit status 1
ayux@pop-os:~/Desktop/httpx/cmd/httpx$ cat ./resume.cfg
resume_from=test.com # wrong input
index=5 # wrong index

after:

ayux@pop-os:~/Desktop/Projects/httpx/cmd/httpx$ echo -e "example.com\ntest.com\nfacebook.com\nhackerone.com\ngoogle.com\n" | go run .

    __    __  __       _  __
   / /_  / /_/ /_____ | |/ /
  / __ \/ __/ __/ __ \|   /
 / / / / /_/ /_/ /_/ /   |
/_/ /_/\__/\__/ .___/_/|_|
             /_/

		projectdiscovery.io

[WRN] UI Dashboard is disabled, Use -dashboard option to enable
https://example.com
https://facebook.com
https://google.com
^C[INF] CTRL+C pressed: Exiting
[INF] Creating resume file: resume.cfg
exit status 1
ayux@pop-os:~/Desktop/Projects/httpx/cmd/httpx$ cat resume.cfg 
resume_from=google.com
index=3

Summary by CodeRabbit

  • Bug Fixes

    • Resume now advances after each output, so resumed runs continue from the last produced result and avoid repeats or gaps.
    • Persisted resume state now reflects the most recently produced output for more reliable continuation.
  • Chores

    • Version control ignore pattern adjusted so resume.cfg is consistently ignored.

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

- Remove `current` and `currentIndex` fields (commented out).
  - Add `completedIndex` (tracks number of results actually printed) and `lastPrinted` (stores the last host that was output).
- Update resume logic in `Runner.RunEnumeration`:
  - Resume state is now updated **after** user‑visible output is written, preventing skipped targets on abrupt interruption.
  - Record `lastPrinted` and increment `completedIndex` when a response is printed.
- Disable the old pre‑output resume tracking in `processItem` (commented out).
- Adjust `SaveResumeConfig` to persist the new fields (`completedIndex` and `lastPrinted`) instead of the removed ones.
- Add detailed comment explaining the rationale for updating resume state at the latest commit point.
@auto-assign auto-assign bot requested a review from dwisiswant0 January 4, 2026 01:12
@coderabbitai
Copy link

coderabbitai bot commented Jan 4, 2026

Walkthrough

Added lastPrinted string to ResumeCfg; resume advancement moved to occur after output is produced and persisted using lastPrinted. .gitignore entry -/resume.cfg was replaced with resume.cfg.

Changes

Cohort / File(s) Summary
Gitignore
/.gitignore
Replaced -/resume.cfg with resume.cfg to ignore resume.cfg broadly
Resume State Structure
runner/resume.go
Added field lastPrinted string to ResumeCfg (other fields like ResumeFrom, Index, current, currentIndex remain)
Resume Logic & Persistence
runner/runner.go
RunEnumeration updates resumeCfg.lastPrinted after emitting/printing each response; SaveResumeConfig persists ResumeFrom from r.options.resumeCfg.lastPrinted rather than current; completed-index increment remains commented

Sequence Diagram(s)

sequenceDiagram
    participant Runner
    participant Output
    participant Persistence
    rect rgba(211,244,255,0.6)
    Note over Runner,Output: Resume advancement deferred until after printing
    end
    Runner->>Output: process input -> produce/print result
    alt printed successfully
        Note right of Output: After successful print, Runner updates resume state
        Output-->>Runner: printed
        Runner->>Persistence: save `lastPrinted` (persist to `resume.cfg`)
        Persistence-->>Runner: ack
    else print error or skip
        Output-->>Runner: error/no advancement
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through lines and found my cue,
I only mark what I have printed true,
lastPrinted whispers where I paused,
A careful hop — no step is lost,
Onward I go, with a tiny view.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix incorrect resume cfg write' directly and specifically describes the main change in the pull request: correcting how resume.cfg data is persisted to disk.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
runner/runner.go (1)

1305-1332: Critical: Resume functionality is now disabled.

Commenting out the resume-skipping logic (lines 1306-1310) means that resume will not skip already-processed items on restart. While the PR description mentions this is a "partial fix," disabling resume skipping entirely breaks the resume feature.

Missing components for resume to work:

  1. Initialization logic: When resume config is loaded, completedIndex and lastPrinted need to be initialized from Index and ResumeFrom.
  2. Skip logic: Code needs to skip processing items until reaching the resume point (based on completedIndex or matching lastPrinted).

The new approach (lines 1099-1113) correctly records progress after output, but without skip logic, users will reprocess all items from the beginning on every resume, defeating the purpose.

🔎 Expected resume flow

When resume is loaded, initialization should occur:

if r.options.ShouldLoadResume() && r.options.resumeCfg.Index > 0 {
    r.options.resumeCfg.completedIndex = r.options.resumeCfg.Index
    r.options.resumeCfg.lastPrinted = r.options.resumeCfg.ResumeFrom
    gologger.Debug().Msgf("Resuming at position %d: %s\n", r.options.resumeCfg.Index, r.options.resumeCfg.ResumeFrom)
}

Then in processItem, skip logic should check:

if r.options.resumeCfg != nil {
    // Skip items until we reach the resume point
    if r.options.resumeCfg.completedIndex > 0 {
        r.options.resumeCfg.completedIndex--
        return nil
    }
}

Or alternatively match against lastPrinted to find the resume point.

🧹 Nitpick comments (1)
runner/resume.go (1)

6-7: Remove commented-out code.

The commented fields current and currentIndex should be deleted entirely rather than left as comments. Commented code clutters the codebase and can cause confusion.

🔎 Proposed cleanup
 type ResumeCfg struct {
 	ResumeFrom string
 	Index      int
-	// current        string
-	// currentIndex   int
 	completedIndex int    // number of results actually printed
 	lastPrinted    string // last host printed
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0cbc12b and d00d98c.

📒 Files selected for processing (3)
  • .gitignore
  • runner/resume.go
  • runner/runner.go
🔇 Additional comments (1)
runner/runner.go (1)

1099-1113: Good approach: updating resume state after output commit point.

The rationale is sound—updating resume state after output is printed prevents skipped targets on abrupt interruption. The resp.Input field reliably contains the original input identifier, as it is consistently set to origInput throughout the codebase, making it suitable for resume tracking.

runner/resume.go Outdated
Comment on lines 6 to 7
// current string
// currentIndex int

Choose a reason for hiding this comment

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

this is breaking change, would be better if you can handle resume cases also

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the review. When I searched the entire codebase to see where those two parameters were actually used for reading and parsing the resume related logic, I couldn’t find any occurrences at all. That left me genuinely confused about how the resume logic is even being parsed.

Copy link
Author

Choose a reason for hiding this comment

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

this is breaking change, would be better if you can handle resume cases also

Please take a look at #2345 (comment). The issue can be reproduced using the current stable release of httpx, and the resume logic is already broken.

Choose a reason for hiding this comment

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

@ayuxsec thanks for looking into the details. Here's what I meant anyone using resume config in SDK or some integration might break if we change the struct behavior. I know this is a partial fix for your issue, but we should ensure backward compatibility.

You can keep removed current since lastPrinted now handles that for saving.

Here's what the skip logic should look like:

  processItem := func(k string) error {
      if r.options.resumeCfg != nil && r.options.ShouldLoadResume() && r.options.resumeCfg.Index > 0 {
          r.options.resumeCfg.currentIndex++
          if r.options.resumeCfg.currentIndex <= r.options.resumeCfg.Index {
              return nil //  here we can skip already processed items
          }
      }
      
      protocol := r.options.protocol
      // ... rest of existing code
  }

do give a try and let me know if it make sense.

runner/runner.go Outdated
Comment on lines 1099 to 1109
// NOTE: Resume state is updated at the *latest commit point* (i.e. after
// user-visible output is produced), not during input ingestion or scheduling.
//
// Reason:
// - Updating resume earlier (when reading from streamChan / processItem)
// causes skipped targets on resume if execution is interrupted.
// - A missed index is acceptable (user may re-scan one host),
// but a skipped index is catastrophic (user silently misses hosts).
//
// Therefore, we only advance resume state after the result has been
// irrevocably committed to output.
Copy link

@knakul853 knakul853 Jan 4, 2026

Choose a reason for hiding this comment

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

comments is nicely explains the bug, but we can keep it short concise eg:
// Update resume state after output to avoid skipping unprocessed targets on interrupt

Co-authored-by: Nakul Bharti <knakul853@gmail.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Fix all issues with AI Agents 🤖
In @runner/resume.go:
- Around line 3-10: Resume currently saves ResumeCfg but never skips processed
items; update processItem to consult r.options.resumeCfg (ResumeFrom/Index)
before work and return early when item should be skipped. Locate the processItem
function and add a check using r.options.resumeCfg: if it's non-nil and either
Index > 0 (use a running counter to compare to Index) or ResumeFrom is set (call
a helper shouldSkip(currentKey, ResumeFrom) or implement equivalent string
comparison), then return nil to avoid reprocessing; ensure completedIndex and
lastPrinted continue to be updated when processing resumes past the skip point.
🧹 Nitpick comments (3)
runner/resume.go (3)

4-5: Clarify the relationship between exported and unexported fields.

The struct now has two sets of fields that appear to track similar information: Index/ResumeFrom (exported) and completedIndex/lastPrinted (unexported). From the context, it appears that the exported fields are used for serialization/deserialization while the unexported fields track runtime state. This dual-field design can be confusing.

Consider adding documentation to clarify:

  • Which fields are persisted to resume.cfg
  • Which fields are used for internal state tracking
  • How the values flow between these field pairs
📝 Example documentation
 type ResumeCfg struct {
+	// ResumeFrom is the last successfully processed target (persisted to disk)
 	ResumeFrom string
+	// Index is the count of successfully processed targets (persisted to disk)
 	Index      int
 	// current        string
 	// currentIndex   int
+	// completedIndex tracks the number of completed items during runtime
 	completedIndex int 
+	// lastPrinted tracks the last printed target during runtime
 	lastPrinted    string
 }

6-7: Remove commented-out code.

The commented-out fields serve no functional purpose and should be removed. Version control preserves the history, so these lines can be safely deleted to reduce clutter.

Based on previous review feedback, the new field names are self-explanatory and don't require these commented lines as reference.

🔎 Proposed change
 	ResumeFrom string
 	Index      int
-	// current        string
-	// currentIndex   int
 	completedIndex int 
 	lastPrinted    string

3-10: Consider separating persistence format from runtime state.

The ResumeCfg struct now serves two purposes:

  1. External persistence format (ResumeFrom, Index - exported for serialization)
  2. Internal runtime state (completedIndex, lastPrinted - unexported)

While the current approach maintains backward compatibility for the file format, mixing these concerns in a single struct can lead to confusion. The dual-field design (where Index is populated from completedIndex and ResumeFrom from lastPrinted) adds cognitive overhead.

If backward compatibility with the existing resume.cfg format is a constraint, the current approach is acceptable. However, if you have the flexibility to refactor later, consider:

  • A separate internal struct for runtime state tracking
  • Clear separation between what's persisted vs. what's transient

For now, the unexported fields appropriately hide implementation details from external consumers.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d00d98c and 93a00fc.

📒 Files selected for processing (1)
  • runner/resume.go

@ayuxsec ayuxsec requested a review from knakul853 January 6, 2026 01:06
Co-authored-by: Nakul Bharti <knakul853@gmail.com>
Copy link

@coderabbitai coderabbitai bot left a comment

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 (1)
runner/runner.go (1)

1099-1103: Consider removing the extra blank line for consistency.

The resume state update logic is correct and the comment is appropriately concise. However, line 1102 contains an unnecessary blank line that could be removed.

🔎 Proposed fix
 // Update resume state after output to avoid skipping unprocessed targets on interrupt
 if r.options.resumeCfg != nil {
     r.options.resumeCfg.lastPrinted = resp.Input
-    
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24b4efe and 5405787.

📒 Files selected for processing (1)
  • runner/runner.go
🔇 Additional comments (1)
runner/runner.go (1)

2588-2593: Resume implementation is sound and no changes are needed.

The SaveResumeConfig function correctly persists the runtime state, and the complete resume flow operates as intended:

  1. ✓ Resume loading properly initializes Index and ResumeFrom from resume.cfg via goconfig.Load()
  2. currentIndex correctly initializes to 0 on restart (default int value), while the loaded Index value controls skip behavior
  3. ✓ Skip logic works correctly: items are skipped while currentIndex <= Index, resuming from the correct position
  4. ✓ State tracking is accurate: lastPrinted is updated after each output and correctly persisted as ResumeFrom

The change to use lastPrinted instead of current is consistent with the resume mechanism and poses no issues.

@ayuxsec ayuxsec requested a review from knakul853 January 6, 2026 19:28
@knakul853
Copy link

knakul853 commented Jan 7, 2026

@ayuxsec thanks for resolving those comments. I tried to test and it seems the resume issue is still there, have you tested this?

@ayuxsec
Copy link
Author

ayuxsec commented Jan 7, 2026

Hi @knakul853 I wasn’t able to understand how resume was parsed earlier (original dev’s intent), so I didn’t change any of the parsing by myself. I only made a partial fix to ensure the last successfully scanned host is written to resume.cfg at the resume_from index.

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.

resume.cfg may be written before the tool fully validates or flushes the current processing

2 participants