← back to Delivery Address Fix
delivery-address-fix skill: engine + ubereats(PROVEN)/doordash/instacart adapters (TK-10145)
4fddd9a436e512244268b009ea5dcce7e616231f · 2026-08-02 15:21:29 -0700 · Steve Abrams
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Files touched
A .gitignoreA SKILL.mdA references/_adapter-template.mdA references/doordash.mdA references/instacart.mdA references/ubereats.mdA scripts/browser-server.js
Diff
commit 4fddd9a436e512244268b009ea5dcce7e616231f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Aug 2 15:21:29 2026 -0700
delivery-address-fix skill: engine + ubereats(PROVEN)/doordash/instacart adapters (TK-10145)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
.gitignore | 8 +++
SKILL.md | 106 ++++++++++++++++++++++++++++++++++++++++
references/_adapter-template.md | 20 ++++++++
references/doordash.md | 29 +++++++++++
references/instacart.md | 28 +++++++++++
references/ubereats.md | 46 +++++++++++++++++
scripts/browser-server.js | 20 ++++++++
7 files changed, 257 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1924158
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
diff --git a/SKILL.md b/SKILL.md
new file mode 100644
index 0000000..bbb2bbf
--- /dev/null
+++ b/SKILL.md
@@ -0,0 +1,106 @@
+---
+name: delivery-address-fix
+description: Pluggable, provider-adapter skill that fixes/saves a delivery address on a food-delivery account (UberEats, DoorDash, Instacart) by driving a REAL headed Chrome through the provider's own site UI, with human-in-the-loop 2FA relayed over email. Use when Steve (or any authorized user) says "change my delivery address on <provider>", "my order is going to the wrong address", "save this address on ubereats/doordash/instacart", "/delivery-address-fix", or replies to an in-flight address-fix thread with a 2FA code, card digits, or an address. Born from the 2026-08-02 UberEats session (TK-10140); generalized per Steve's directive ("The system must work for all users. Allow user to plugin this skill for ubereats or Instacart and door dash", 2026-08-02, TK-10145).
+---
+
+# delivery-address-fix — pluggable delivery-address fixer
+
+One engine, N provider adapters. The engine owns the browser session, the
+human-in-the-loop (HITL) email relay, and the hard rails. Each adapter
+(`references/<provider>.md`) owns the provider-specific selectors and flow.
+
+## Architecture
+
+```
+user email/request
+ │
+ ▼
+ ENGINE (this file) ADAPTERS (references/)
+ - session lifecycle ──► ubereats.md (PROVEN 2026-08-02)
+ - HITL email relay doordash.md (scaffold — verify on first run)
+ - hard rails instacart.md (scaffold — verify on first run)
+ - screenshots/audit
+```
+
+## Engine: session lifecycle
+
+1. **Workdir per run**: `~/delivery-address-fix/<provider>-<yyyymmdd>/`
+ (screenshots, step scripts, HANDOFF.md). Keep a HANDOFF.md updated after
+ EVERY state change so any later session can resume cold.
+2. **Browser**: long-lived headed REAL Chrome via Playwright persistent
+ context — launcher template in `scripts/browser-server.js` (edit profile
+ dir, CDP port, start URL per provider; one port per provider, 9223+).
+ Headless gets bot-walled; headed real Chrome is the proven path.
+3. **Connect for each step over CDP** (`http://127.0.0.1:<port>`) with short
+ scripts — never restart the browser between steps; the parked session IS
+ the state.
+4. **⚠️ Select the target tab by URL substring** (e.g. `uber.com`), NEVER
+ `pages()[0]` — other tasks open tabs in the same browser (proven failure
+ 2026-08-02: a Tesla auth tab became index 0).
+
+## Engine: HITL email relay (2FA, card gates, address)
+
+The user is remote; codes and card digits arrive by email reply, gated by the
+sender allowlist + secret phrase (per the mailbox's standing rules).
+
+- **Code gates (SMS/call/email) EXPIRE FAST** — Uber sessions died in well
+ under 30 min. Enter codes the moment they land. If expired: restart the
+ auth flow (adapter has the script pattern), tell the user the NEWEST code
+ wins, and that any resend INVALIDATES prior codes. Never resend
+ speculatively — only when the user says the code didn't arrive.
+- **Static gates (card-digit verification) do NOT expire** — park and wait.
+ NEVER guess digits, NEVER submit blank (risks killing the session). If the
+ reply contains no digits, re-ask specifying exactly which digits.
+- **Email-code self-read**: if the provider offers an email code and the
+ user's inbox is George-readable, read it yourself (strip `<style>` blocks;
+ beware numbers in the provider's footer address masquerading as codes).
+- Every ask to the user states: what's needed, the exact reply format
+ ("1234 <secret-phrase>"), and that sooner is safer.
+
+## HARD RAILS (engine-level, all providers)
+
+1. **VERBATIM ADDRESS ONLY.** Save exactly the address the user typed —
+ street, city, zip, verbatim. NEVER infer an address from location data,
+ map readings, charger positions, or context. (2026-08-02 lesson: an
+ inferred "668 S Alameda St" from a Supercharger reading was WRONG —
+ "I have never been at alameda!!". If the user hasn't stated the address,
+ ask for it.)
+2. **Never place, cancel, or modify orders** without an explicit per-action
+ go. Address save ≠ order authority.
+3. **Surface before deleting**: list any existing saved addresses that look
+ wrong to the user before removing anything.
+4. **Live-order first**: after login, check for an active order; if it shows
+ a wrong address, use the provider's order-Help change-address path (if
+ offered) before touching the address book.
+5. **Screenshot every step** into the workdir; update HANDOFF.md.
+6. **Credentials**: only from the secrets master (`~/Projects/secrets-manager/.env`)
+ or the user's explicit reply. Never echo full card numbers/codes back —
+ last-4 only.
+7. **Ticket everything** (`tk`); cost line on every report ($0 local browser).
+
+## Multi-user plugin model (Kickback.ai direction)
+
+Per-user config object — everything user-specific is parameterized, nothing
+hardcoded:
+
+```json
+{
+ "user_id": "steve",
+ "provider": "ubereats | doordash | instacart",
+ "login_email": "…",
+ "email_reader": "george:steve-personal | none",
+ "reply_gate": { "allowlist": ["…"], "secret": "…" },
+ "address": { "street": "…", "city": "…", "zip": "…", "nickname": "…" },
+ "cdp_port": 9223
+}
+```
+
+The engine + adapters read ONLY from this config. A hosted/product version
+(Kickback.ai) swaps the email relay for an in-app prompt but keeps the same
+adapter contract: `login() → passGates() → liveOrderCheck() → saveAddress()`.
+
+## Adding a provider
+
+Copy `references/_adapter-template.md`, fill in: auth URL, login selectors,
+2FA gate types observed, address-book path, order-help path, quirks. Mark
+UNVERIFIED until a real run proves each step; promote to PROVEN with dates.
diff --git a/references/_adapter-template.md b/references/_adapter-template.md
new file mode 100644
index 0000000..91e5d82
--- /dev/null
+++ b/references/_adapter-template.md
@@ -0,0 +1,20 @@
+# <Provider> adapter — SCAFFOLD (UNVERIFIED)
+
+Status: UNVERIFIED / PROVEN <date>. Promote only after a real run proves
+each step; note the date per proven step.
+
+## Auth flow
+1. Entry URL:
+2. Identity step (email/phone):
+3. Gate types observed (email OTP / SMS OTP / call / card-digits / captcha):
+4. Expiry behavior + recovery script pattern:
+
+## After login
+1. Live-order check path:
+2. Active-order address-change path (if any):
+3. Address book path + add-address mechanics (autocomplete? pin editor?):
+
+## Quirks
+- Tab-selection URL substring:
+- CDP port:
+- Bot-wall notes:
diff --git a/references/doordash.md b/references/doordash.md
new file mode 100644
index 0000000..fd72e78
--- /dev/null
+++ b/references/doordash.md
@@ -0,0 +1,29 @@
+# DoorDash adapter — SCAFFOLD (UNVERIFIED)
+
+Status: no live run yet. Every selector below must be verified against the
+real site on first run, then this file promoted to PROVEN with dates.
+
+## Expected auth flow
+
+1. `https://www.doordash.com/` → Sign In → `identity.doordash.com`.
+2. Email or phone entry → continue.
+3. Gate types to expect (verify): email OTP, SMS OTP. DoorDash is known to
+ bot-wall aggressively (PerimeterX family) — headed real Chrome mandatory;
+ if walled, the openclaw-real-chrome fallback pattern applies.
+4. No card-digit gate reported for login (verify).
+
+## After login
+
+1. Live-order check: home page banner / Orders tab shows active orders.
+ DoorDash DOES allow address change on an active order in some windows via
+ order Help → "Change address" (verify availability per order state).
+2. Address book: Account → Addresses (`/account/addresses`) → Add Address —
+ search field autocompletes; entering a verbatim street+zip then selecting
+ the exact match satisfies the verbatim rule. Supports apt/suite + drop-off
+ instructions + pin adjust.
+
+## Quirks (to confirm on first run)
+
+- Persistent-context profile reuse may keep the session alive for weeks —
+ prefer reusing the profile before re-running login gates.
+- CDP port suggestion: 9224 (keep one port per provider).
diff --git a/references/instacart.md b/references/instacart.md
new file mode 100644
index 0000000..4903c0d
--- /dev/null
+++ b/references/instacart.md
@@ -0,0 +1,28 @@
+# Instacart adapter — SCAFFOLD (UNVERIFIED)
+
+Status: no live run yet. Every selector below must be verified against the
+real site on first run, then this file promoted to PROVEN with dates.
+
+## Expected auth flow
+
+1. `https://www.instacart.com/` → Log in.
+2. Email entry → Instacart favors passwordless email OTP ("Enter the code we
+ sent to …"); password login exists behind "Use password instead" (verify).
+3. Gate types to expect: email OTP (George-readable if it's the user's
+ inbox), occasional SMS OTP.
+4. No card-digit login gate reported (verify).
+
+## After login
+
+1. Live-order check: Orders page shows active deliveries. Instacart address
+ change on an ACTIVE order is generally NOT self-serve once shopping
+ started — surfaces to care chat (do not attempt without explicit go).
+2. Address book: Account → Addresses → Add new address — autocomplete search;
+ select the exact verbatim match; supports unit + delivery instructions.
+ New orders pick the address at checkout — confirm the default flips to
+ the saved one (verify).
+
+## Quirks (to confirm on first run)
+
+- CDP port suggestion: 9225.
+- Instacart sessions are long-lived once the profile persists (verify).
diff --git a/references/ubereats.md b/references/ubereats.md
new file mode 100644
index 0000000..15a0232
--- /dev/null
+++ b/references/ubereats.md
@@ -0,0 +1,46 @@
+# UberEats adapter — PROVEN 2026-08-02 (TK-10140)
+
+Status: every step below was exercised in the live 2026-08-02 session
+(`~/uber-address-fix/`, screenshots + step scripts there are the reference
+implementation).
+
+## Auth flow (auth.uber.com)
+
+1. Go to `https://www.ubereats.com/` → sign in → lands on `auth.uber.com`.
+2. "What's your phone number or email?" → enter login email → Next
+ (pattern: `step3.js`).
+3. **Email code** (sometimes offered first): read from the user's inbox via
+ George (`step4–6.js`). Strip `<style>` blocks from the HTML; Uber's street
+ address ("1725 …") appears in the footer — do NOT mistake it for the code.
+4. **SMS / phone-call 4-digit code**: 4 separate single-digit inputs, then
+ Next (`step6.js` pattern). Gate behaviors, all observed:
+ - Codes + whole auth sessions expire in well under 30 min → "Bad request —
+ Current session has timed out" even with a correct code.
+ - Recovery: `step8-restart-flow.js` — reload auth.uber.com, resubmit the
+ email; Uber usually skips straight to a fresh 2FA and auto-fires a code.
+ - "Resend code via SMS" can rate-limit → "Error sending code / Bad
+ request / Please use another method"; the restart flow then tends to
+ fall over to PHONE CALL ("Call me with code") or EMAIL code.
+ - Every resend/re-fire INVALIDATES prior codes — newest wins, tell the user.
+5. **Card-verification gate** (final, static — does not expire): "confirm
+ your payment card's missing digits" — shows brand + last-4 (e.g.
+ AMERICAN EXPRESS `____-5001`), one text input + Next. Wants the 4 digits
+ immediately BEFORE the last-4 (on a 15-digit Amex: digits 8–11). No
+ alternate path exists (verified via full element dump). Never guess,
+ never submit blank.
+
+## After login (redirects to ubereats.com)
+
+1. Check for a LIVE order. Wrong address on an active order → order Help →
+ change delivery address if offered.
+2. Account → Addresses → add the user's verbatim address, set nickname; if
+ the pin editor appears, place the pin per the user's instruction.
+3. Screenshot each step; email the user the result.
+
+## Quirks
+
+- Select the Uber tab by URL containing `uber.com`, never `pages()[0]`
+ (`check-uber-tab.js`).
+- Headed real Chrome (channel: 'chrome') required; profile persists in the
+ workdir so a dead process can relaunch without full re-login (sometimes).
+- CDP port used: 9223.
diff --git a/scripts/browser-server.js b/scripts/browser-server.js
new file mode 100644
index 0000000..4dba847
--- /dev/null
+++ b/scripts/browser-server.js
@@ -0,0 +1,20 @@
+// Long-lived headed real-Chrome session with CDP for step scripts.
+// Generic launcher: node browser-server.js <profileDir> <cdpPort> <startUrl>
+const { chromium } = require('playwright');
+const [profileDir, cdpPort, startUrl] = process.argv.slice(2);
+if (!profileDir || !cdpPort || !startUrl) {
+ console.error('usage: node browser-server.js <profileDir> <cdpPort> <startUrl>');
+ process.exit(1);
+}
+(async () => {
+ const ctx = await chromium.launchPersistentContext(profileDir, {
+ channel: 'chrome',
+ headless: false,
+ viewport: { width: 1360, height: 900 },
+ args: [`--remote-debugging-port=${cdpPort}`],
+ });
+ const page = ctx.pages()[0] || (await ctx.newPage());
+ await page.goto(startUrl, { waitUntil: 'domcontentloaded', timeout: 60000 }).catch(e => console.log('nav err', e.message));
+ console.log('READY', page.url());
+ await new Promise(() => {});
+})().catch(e => { console.error('LAUNCH FAIL', e.message); process.exit(1); });
(oldest)
·
back to Delivery Address Fix
·
absorb delivery-address-skill (TK-10144): generic launch/sta f519a12 →