| name | rescan-skipped |
| description | Re-score a campaign's skipped jobs and promote the eligible ones to `approved` for later applying. Recovers jobs wrongly dropped for location, a sparse JD, 1099, or seniority. Does not apply. |
| argument-hint | <campaign-id> [--jobs key1,key2,…] |
Rescan Skipped - Recover Wrongly-Dropped Jobs
Re-score a campaign's skipped jobs and set eligible ones to approved. Never apply and never change the campaign's status - apply the promoted jobs afterward via the apply skill (apply campaign <campaign-id>) or the campaign page.
Setup
Follow ../_shared/setup.md. Fetch the campaign: curl -fsS -H "authorization: Bearer $JOBPILOT_API_TOKEN" "$JOBPILOT_API/api/campaigns/<campaign-id>". Threshold = config.minScore (fallback autoApply.minMatchScore, else 60).
Step 1: Select Targets
Targets are every status:"skipped" job; with --jobs key1,key2,…, restrict to those keys.
- Always leave (permanent) - only these:
skipReason starting Already applied, CAPTCHA, Payment required, or No visa sponsorship (JD:, or one stating a JD-cited citizenship/clearance requirement.
- Whole-campaign mode (no
--jobs): also leave deliberate user choices - Removed by user, Not selected by user, User cancelled…, Max applications limit reached, Campaign paused by user.
--jobs mode: reconsider every named target except the permanent ones.
Count the full target list up front and process every one. Below-threshold, zero-score, and no-skipReason jobs are all targets - the stored score came from the campaign that wrongly skipped them, so it's never a reason to skip the re-score. Don't cherry-pick the jobs already at/above threshold.
Step 2: Per Job
-
Digest - parse the cached digest. Rich = non-empty skills and requirements/responsibilities.
-
Re-read only when needed - if the digest is thin/empty, or the original skipReason was invalid (location/onsite, sparse JD, 1099, seniority), open the posting (browser_navigate + narrowed browser_snapshot; log in via ../_shared/auth.md if walled) and rebuild the digest. Send that digest and posting text with the rescan command below; terminal rows cannot be PATCHed.
-
Re-score - every target gets a fresh POST /api/score-fit with {digest, minScore:<threshold>}; never reuse the stored matchScore. Take the returned score as-is when verdict is trust; on deliberate, reason from strongMatches/partialMatches/gaps. A zero/low score with no skipReason (common at defense/federal employers) is not a disqualifier - only a JD-stated citizenship/clearance or no-sponsorship bar is (never infer from industry).
-
Decide:
- Eligible and
score >= threshold → promote (no apply):
curl -fsS -H "authorization: Bearer $JOBPILOT_API_TOKEN" -X POST "$JOBPILOT_API/api/campaigns/<campaign-id>/jobs/<key>/rescan" \
-H 'content-type: application/json' \
-d "$(jq -n --argjson score <0-100> --arg reason "<one line>" --arg digest "$DIGEST" --arg desc "<posting text or empty>" \
'{decision:"approved", matchScore:$score, matchReason:$reason, digest:$digest, description:$desc}')"
- Below threshold after a fair read → POST
/rescan with decision:"skipped", the new score/reason, and skipReason:"Below minimum match score (X < Y)".
- JD-stated citizenship/clearance or no-sponsorship language found on re-read → POST
/rescan with decision:"skipped" and that eligibility reason.
Step 3: Eligibility
Follow ../_shared/eligibility.md - seniority/below-level, location/onsite, sparse JDs, and 1099/contractor are never skips; only a JD-stated citizenship/clearance requirement - or, when the profile requires sponsorship, JD-stated no-sponsorship language - disqualifies.
Step 4: Finish
Process every target before finishing - promoted + left-skipped + permanent must equal the target count. If any are unprocessed, keep going; don't report a partial pass as complete.
Print a short table (promoted vs left skipped, with reasons) plus the reconciliation (e.g. "228 skipped → 226 targets; 19 promoted, 207 left skipped"). Then point the user to apply campaign <campaign-id> (or the campaign page's Apply selected). Don't apply; don't change campaign status.