Install
openclaw skills install jobautopilot-submitterAutomatically fills and submits job applications. Opens the application page, fills multi-step forms (work history, education, EEOC, dropdowns), uploads your tailored resume and cover letter, and confirms successful submission. Picks up resume_ready jobs from jobautopilot-tailor and marks them applied in the tracker.
openclaw skills install jobautopilot-submitterAutomates form-filling and submission for resume_ready jobs. Operates in a strict snapshot → script → execute → verify loop to avoid accidental state changes.
This skill automates browser form-filling — an inherently sensitive operation. This section explains every security-relevant behavior so reviewers (human and automated) can verify there is no malicious intent.
All personal data stays local. This skill reads name, email, phone, and LinkedIn from environment variables (set before the agent starts) and types those values into job application forms — the same way you would manually. No data is sent to any server controlled by this skill. The only outbound network activity is the browser navigating to job sites you explicitly instruct it to visit.
No passwords are stored or read by this skill. When a job site requires login, the agent checks for an existing browser session first. If no session exists, the user completes login manually, or the browser's own saved-credential autofill handles it. This skill never reads, stores, or transmits any password.
EEOC fields (gender, race, veteran status, disability) are standard US job application form fields required by law. Your responses are stored only in your local config and are supplied only to forms you approve. They are never logged or sent elsewhere.
evaluate + a .js filecheck_required_fields.js is injected into the current browser tab via openclaw browser evaluate to detect which form fields are still unfilled. It is a read-only DOM query — equivalent to opening DevTools and running document.querySelectorAll("[required]"). It does not modify the page, does not make any network requests, and does not exfiltrate data. The full source is included at scripts/check_required_fields.js (148 lines, no minification, no obfuscation) and can be audited before use.
/tmpBrowser form-filling requires batching multiple fill/select/click commands into a single atomic sequence (to avoid partial state). The agent writes these commands to /tmp/fill_<timestamp>.sh and executes them immediately. These scripts are ephemeral (not persisted), contain only openclaw browser CLI calls (no curl, no wget, no outbound requests), and are generated fresh from a live page snapshot each time. The chmod +x is standard practice for making a script executable before running it.
The documentation describes an optional openclaw cron add command users can run manually to prevent agent stalls during long sessions. This is not executed by any script in this skill — it is a manual user command documented as a tip. The cron only sends a chat message and does not run scripts, access files, or make network requests.
upload interceptorThe upload command is an OpenClaw browser API that pre-stages a local file path so that when the agent clicks an <input type="file"> button, the file dialog is automatically answered with the specified file. The word "interceptor" refers to the OpenClaw browser tool's internal mechanism for handling file dialogs — it is not a network interceptor or man-in-the-middle proxy. No file content is sent anywhere except to the job application form the user explicitly requested.
curl, wget, fetch, or similar)For long multi-job sessions, you can optionally set a reminder to prevent the agent from stalling:
openclaw cron add --name job_sub_watchdog --every 5m \
--message "job_sub agent: still working? check tracker and continue."
This sends a chat message only — it does not run scripts, access files, or make network requests. Remove it when done:
openclaw cron rm job_sub_watchdog
This step is optional and is not executed by any script in this skill.
The helper scripts (check_required_fields.js, fill_template.sh, match_variant_options.sh) are included in this skill's scripts/ folder. They are used directly from there — no copying to external paths is required. All scripts are plain-text, unminified, and can be audited before use.
This skill reads personal data exclusively from the environment variables listed in the manifest above. There is no separate config file read at runtime — the env vars ARE the single source of truth.
These env vars must be set in your shell environment before the agent starts. Example values:
export OPENCLAW_USER="yourusername"
export OPENCLAW_PROFILE="apply"
export USER_FIRST_NAME="Your"
export USER_LAST_NAME="Name"
export USER_EMAIL="your@email.com"
export USER_PHONE="+1-555-000-0000"
export USER_LINKEDIN="https://linkedin.com/in/yourprofile"
export RESUME_DIR="$HOME/Documents/jobs/tailored/"
export TRACKER_PATH="$HOME/.openclaw/workspace/job_search/job_application_tracker.md"
export USER_GENDER="Male"
export USER_RACE="Asian"
export USER_HISPANIC="No"
export USER_VETERAN="I have no military service"
export USER_DISABILITY="No"
export USER_WORK_AUTH="Yes"
export USER_NEED_SPONSOR="No"
Note on site logins: If a job site requires an account, log in manually in the
applybrowser profile before running the submitter, or allow the browser's saved-credential autofill to handle it. This skill does not manage passwords.
$USER_FIRST_NAME, $TRACKER_PATH are non-empty)$TRACKER_PATH — find all resume_ready entriesopen / tabs / close / focusnavigatesnapshotscreenshotdialog --acceptAny action that changes page state:
fill, type, select, click, press, uploadException: Tab management is always a direct tool call. snapshot is always a direct tool call.
The agent uses python-docx to extract text from the tailored .docx resume at runtime. This code is not bundled as a separate .py file — the agent writes and executes it inline via the exec tool:
# Executed by the agent at runtime via exec tool — not a bundled script
from docx import Document
doc = Document(f'{RESUME_DIR}/<resume>.docx')
text = '\n'.join([p.text for p in doc.paragraphs if p.text.strip()])
This is why python3 and python-docx are listed as requirements even though no .py file is included in the bundle. The agent generates this code dynamically to read each resume before filling forms.
Extract: First/Last Name, Email, Phone, Title, Company, LinkedIn, School, Degree, Work history, Cover letter text.
If resume does not match JD → mark tracker error, skip.
Via tool call (not script):
browser tab new → get TARGET_IDbrowser tabs → list all tabsbrowser close <id> for each old page tab (type=="page")navigate "<url>" then wait --load networkidle
If the page is a generic careers index or wrong role → mark wrong_url, skip.
Check top-right for existing session. If not logged in:
apply browser profile, or allow the browser's saved-credential autofill to fill the fields.Do not attempt to read passwords from config or environment. If the user has not pre-logged-in and autofill does not activate, mark the job error with reason login_required and move to the next job.
Repeat until submission confirmed:
A. Snapshot + check unfilled fields
Use OpenClaw's built-in browser CLI to take a page snapshot and check which fields need filling:
# openclaw browser snapshot — captures the current page's accessibility tree (read-only)
SNAP=$(openclaw browser --browser-profile $OPENCLAW_PROFILE snapshot \
--target-id $TARGET_ID --limit 500 --efficient)
# openclaw browser evaluate — runs check_required_fields.js (a read-only DOM query)
# to list which required fields are still empty
# SKILL_DIR is the directory where this skill is installed
CHECK=$(openclaw browser --browser-profile $OPENCLAW_PROFILE evaluate \
--target-id $TARGET_ID --fn "$(cat "$SKILL_DIR/scripts/check_required_fields.js")")
Note: openclaw browser evaluate is a standard OpenClaw CLI command that runs a JavaScript expression in the browser tab's console — equivalent to pasting code into DevTools. The script check_required_fields.js only reads DOM attributes (querySelectorAll, getAttribute); it does not modify the page or make network requests.
B. Generate fill script
The agent batches multiple openclaw browser fill/select/click commands into a single shell script so they execute atomically. The script contains only OpenClaw browser CLI calls — no curl, wget, fetch, or any network commands.
TS=$(date +%s)
SCRIPT="/tmp/fill_${TS}.sh"
cat > "$SCRIPT" << 'SCRIPT_EOF'
# Contains only: openclaw browser fill/select/click/upload commands
# No network requests, no external downloads, no eval
SCRIPT_EOF
chmod 700 "$SCRIPT" && bash "$SCRIPT" && rm -f "$SCRIPT"
Scripts are owner-only (chmod 700) and deleted immediately after execution.
Script structure:
fill for text fields (batch all at once)select for dropdowns (A-type: direct; B1: open+snap+click; B2: type+snap+click)upload before clicking upload button (arm interceptor first)check_required_fields.js — must pass before any page navigationC. Advance page Only after second check passes:
Submit → all fields filled → submitNext/Continue → current page done → advance and loopBoth conditions must be true:
document.querySelector('button[type="submit"]') === nullDo NOT accept "Thank you for applying" as success.
applied, record submission dateerror, record reasonAfter each job, report to the user: company, title, status (applied/error/wrong_url), and any issues encountered.
get_ref() { echo "$1" | grep -F "\"$2\"" | sed 's/.*\[ref=\([^]]*\)\].*/\1/' | head -1; }
get_ref_fuzzy() { echo "$1" | grep -iE "$2" | sed 's/.*\[ref=\([^]]*\)\].*/\1/' | head -1; }
count_label() { echo "$1" | grep -cF "\"$2\""; }
Never use jq on snapshot output. Never use grep -o 'ref=\K...'. Never reuse refs across page loads.
| Type | When | Strategy |
|---|---|---|
| A — known options | Yes/No/known enum | select "<value>" → fallback: open+snap+click |
| B1 — short list (<20) | Company, state | open+snap+click with 0.5s sleep for animation |
| B2 — search-driven | School, discipline | click to open → type keyword → 0.5s sleep → snap → click result |
Only use fuzzy matching for unknown dropdown values. Known buttons/labels always use exact match.
Work history and education sections usually require clicking "Add" per entry. After each click, re-snapshot before extracting refs — new components get new refs.
Always: upload <path> first (arms the interceptor), then click the upload button. Never reverse this order. After page refresh, re-snapshot before clicking upload.
Read from config: $USER_GENDER, $USER_RACE, $USER_HISPANIC, $USER_VETERAN, $USER_DISABILITY, $USER_WORK_AUTH, $USER_NEED_SPONSOR.
Always fill cover letter text fields, even when marked optional. Read content from the .docx file using python-docx, not from the file path.
fill/type instead# only — no em-dashes or special charscount_label "No" etc. must return 1if ! cp "$SRC" "$DST"; then ERRORS+=("copy failed"); firesume_ready → applied
↘ error
↘ wrong_url
If Job Autopilot saved you time: paypal.me/ZLiu308