{"id":"5e124e02-e21c-49d0-b2b2-92d622e2709c","entityType":"agent","slug":"clawhub-psyb0t-proxq","name":"proxq","canonicalUrl":"https://www.xpersona.co/agent/clawhub-psyb0t-proxq","canonicalPath":"/agent/clawhub-psyb0t-proxq","generatedAt":"2026-10-11T03:54:57.260Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:48:59.807Z","emptyReason":null},"description":"Go, Redis-backed async HTTP proxy queue (built on asynq). POST any HTTP request (any method, any path, any body) to a configured upstream, get a job ID back instantly (202), a worker forwards it later, you poll GET /__jobs/{id} for status (queued/running/completed/failed) and GET /__jobs/{id}/content for the replayed upstream response (status/headers/body). DELETE /__jobs/{id} cancels. Path-prefix routing to multiple upstreams, per-upstream timeout/retries/pathFilter, optional response caching (memory or Redis LRU), automatic direct-proxy bypass for WebSocket/chunked/large-body requests. No built-in auth. Use when the user wants to turn a slow/unreliable backend into a fire-and-forget async API, decouple a client from upstream latency, relay webhooks with retries, or queue heavy uploads/processing jobs behind short-timeout reverse proxies.","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.2K downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s17fq93tmpky791n7516jcn08n83sfn2:proxq","sourceUrl":"https://clawhub.ai/psyb0t/proxq","homepage":"https://clawhub.ai/psyb0t/skills/proxq","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/psyb0t/proxq","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/psyb0t/skills/proxq","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":62,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"proxq technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:48:59.807Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:48:59.807Z","emptyReason":null},"stars":null,"forks":null,"downloads":1200,"packageName":null,"latestVersion":"0.11.4","tractionLabel":"1.2K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:48:59.736Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T01:48:59.807Z","lastCrawledAt":"2026-10-11T01:48:59.736Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T01:48:59.736Z","lastVerifiedAt":null,"highlights":[{"version":"0.11.4","createdAt":"2026-10-10T15:25:29.937Z","changelog":"- Removed the file skill-card.md. - No feature or behavioral changes to the proxq skill itself.","fileCount":4,"zipByteSize":9216},{"version":"0.11.1","createdAt":"2026-09-26T02:32:02.876Z","changelog":"- Removed the file: skill-card.md - Updated documentation in references/setup.md - No functional or API changes; this is a documentation and cleanup update only","fileCount":4,"zipByteSize":9296},{"version":"0.10.13","createdAt":"2026-08-08T21:35:20.533Z","changelog":"- Removed obsolete skill-card.md file. - Updated references/setup.md (details not specified). - No changes to core functionality or user-facing documentation. - Cleanup and minor maintenance for repository organization.","fileCount":4,"zipByteSize":9488},{"version":"0.10.12","createdAt":"2026-08-08T15:09:32.844Z","changelog":"- Removed the file skill-card.md. - No functional or user-facing changes.","fileCount":4,"zipByteSize":9472},{"version":"0.10.11","createdAt":"2026-08-08T10:02:08.526Z","changelog":"proxq 0.10.11 - Updated example usage in documentation: `$UPSTREAM_TOKEN` is now shown as a variable in request header examples. - Removed obsolete skill-card.md file.","fileCount":4,"zipByteSize":9465},{"version":"0.10.10","createdAt":"2026-08-01T20:33:08.917Z","changelog":"- Removed the file skill-card.md. - No user-facing functionality changes; documentation and core usage remain the same.","fileCount":4,"zipByteSize":9471},{"version":"0.10.9","createdAt":"2026-07-27T23:39:53.461Z","changelog":"- Removed the skill-card.md file. - No user-facing changes to functionality or documentation. - Internal documentation/metadata cleanup only.","fileCount":4,"zipByteSize":9383},{"version":"0.10.8","createdAt":"2026-07-27T23:09:11.316Z","changelog":"- Removed the skill-card.md file. - No user-facing feature changes; documentation and functionality remain unchanged.","fileCount":4,"zipByteSize":9573}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17fq93tmpky791n7516jcn08n83sfn2:proxq","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s17fq93tmpky791n7516jcn08n83sfn2:proxq` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/psyb0t/proxq before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-proxq/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-proxq/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-proxq/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-proxq/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-proxq/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-proxq/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-11T03:54:57.256Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-proxq/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-proxq/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-proxq/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-proxq/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:48:59.807Z","emptyReason":null},"readme":"Skill: proxq\n\nOwner: psyb0t\n\nSummary: Go, Redis-backed async HTTP proxy queue (built on asynq). POST any HTTP request (any method, any path, any body) to a configured upstream, get a job ID back instantly (202), a worker forwards it later, you poll GET /__jobs/{id} for status (queued/running/completed/failed) and GET /__jobs/{id}/content for the replayed upstream response (status/headers/body). DELETE /__jobs/{id} cancels. Path-prefix routing to multiple upstreams, per-upstream timeout/retries/pathFilter, optional response caching (memory or Redis LRU), automatic direct-proxy bypass for WebSocket/chunked/large-body requests. No built-in auth. Use when the user wants to turn a slow/unreliable backend into a fire-and-forget async API, decouple a client from upstream latency, relay webhooks with retries, or queue heavy uploads/processing jobs behind short-timeout reverse proxies.\n\nTags: latest:0.11.4\n\nVersion history:\n\nv0.11.4 | 2026-10-10T15:25:29.937Z | auto\n\n- Removed the file skill-card.md.\n- No feature or behavioral changes to the proxq skill itself.\n\nv0.11.1 | 2026-09-26T02:32:02.876Z | auto\n\n- Removed the file: skill-card.md\n- Updated documentation in references/setup.md\n- No functional or API changes; this is a documentation and cleanup update only\n\nv0.10.13 | 2026-08-08T21:35:20.533Z | auto\n\n- Removed obsolete skill-card.md file.\n- Updated references/setup.md (details not specified).\n- No changes to core functionality or user-facing documentation.\n- Cleanup and minor maintenance for repository organization.\n\nv0.10.12 | 2026-08-08T15:09:32.844Z | auto\n\n- Removed the file skill-card.md.\n- No functional or user-facing changes.\n\nv0.10.11 | 2026-08-08T10:02:08.526Z | auto\n\nproxq 0.10.11\n\n- Updated example usage in documentation: `$UPSTREAM_TOKEN` is now shown as a variable in request header examples.\n- Removed obsolete skill-card.md file.\n\nv0.10.10 | 2026-08-01T20:33:08.917Z | auto\n\n- Removed the file skill-card.md.\n- No user-facing functionality changes; documentation and core usage remain the same.\n\nv0.10.9 | 2026-07-27T23:39:53.461Z | auto\n\n- Removed the skill-card.md file.\n- No user-facing changes to functionality or documentation.\n- Internal documentation/metadata cleanup only.\n\nv0.10.8 | 2026-07-27T23:09:11.316Z | auto\n\n- Removed the skill-card.md file.\n- No user-facing feature changes; documentation and functionality remain unchanged.\n\nv0.10.7 | 2026-07-27T15:22:41.508Z | auto\n\n- Removed the file skill-card.md.\n- No other user-facing functionality or usage changes.\n\nv0.10.6 | 2026-07-27T14:25:09.310Z | auto\n\n## proxq 0.10.6\n\n- No code or documentation changes detected in this version.\n- All functionality, usage, and security guidance remain unchanged.\n\nv0.10.5 | 2026-07-27T13:52:01.953Z | auto\n\n- Removed the file skill-card.md.  \n- No functional or behavioral changes to the skill itself.  \n- Documentation and usage details are unaffected.\n\nv0.10.4 | 2026-07-27T13:28:55.598Z | auto\n\n- Removed the file skill-card.md.\n- No changes to user-visible features or functionality.\n- No modifications in usage, configuration, or security details.\n- This update is purely structural/organizational.\n\nv0.10.3 | 2026-07-26T02:33:16.028Z | auto\n\n## proxq 0.10.3 changelog\n\n- Refined job cancellation security note in documentation: clarified that DELETE cancels are best-effort, with no undo, and should only be used for jobs you submitted or the user explicitly named.\n- Removed repeated or overly detailed warnings about mass/bulk cancellation and job ownership from SKILL.md.\n- Deleted the skill-card.md file for a simpler project structure.\n- No changes to core logic or feature set; documentation cleanup only.\n\nv0.10.2 | 2026-07-26T01:34:03.398Z | auto\n\nVersion 0.10.2\n\n- Added a prominent warning that DELETE /__jobs/{id} is destructive, irreversible, and has no ownership check, requiring explicit user intent for any cancellation.\n- Clarified that cancellation can affect other users’ jobs on shared/multi-tenant instances and should be restricted to admin-only or strongly confirmed cases.\n- Removed the skill-card.md file (no impact on usage).\n- No changes to endpoint behavior or core functionality.\n\nv0.10.1 | 2026-07-26T00:02:23.736Z | auto\n\nproxq 0.10.1\n\n- Initial public version: async HTTP proxy queue built on Go, Redis, and asynq.\n- Instantly queues any HTTP request to a configured upstream; returns job ID for polling.\n- Supports status polling, fetching replayed upstream responses, and job cancellation via simple endpoints.\n- Path-prefix routing for multiple upstreams, with per-upstream timeout, retries, and optional path filtering.\n- Optional in-memory or Redis LRU response caching; direct-proxy bypass for WebSockets, chunked, or large-body requests.\n- **Security:** No built-in authentication; users must secure access via external methods. SSRF risk, so restrict upstreams and network access appropriately.\n\nArchive index:\n\nArchive v0.11.4: 4 files, 9216 bytes\n\nFiles: references/setup.md (8515b), skill-card.md (1870b), SKILL.md (8743b), _meta.json (125b)\n\nFile v0.11.4:SKILL.md\n\n---\nname: proxq\ndescription: Go, Redis-backed async HTTP proxy queue (built on asynq). POST any HTTP request (any method, any path, any body) to a configured upstream, get a job ID back instantly (202), a worker forwards it later, you poll GET /__jobs/{id} for status (queued/running/completed/failed) and GET /__jobs/{id}/content for the replayed upstream response (status/headers/body). DELETE /__jobs/{id} cancels. Path-prefix routing to multiple upstreams, per-upstream timeout/retries/pathFilter, optional response caching (memory or Redis LRU), automatic direct-proxy bypass for WebSocket/chunked/large-body requests. No built-in auth. Use when the user wants to turn a slow/unreliable backend into a fire-and-forget async API, decouple a client from upstream latency, relay webhooks with retries, or queue heavy uploads/processing jobs behind short-timeout reverse proxies.\nhomepage: https://github.com/psyb0t/docker-proxq\nuser-invocable: true\nmetadata:\n  { \"openclaw\": { \"emoji\": \"🦡\", \"primaryEnv\": \"PROXQ_URL\", \"requires\": { \"bins\": [\"curl\", \"docker\"] } } }\npermissions:\n  network: \"outbound HTTP to the configured PROXQ_URL (submit/poll/cancel job calls) — AND proxq itself makes arbitrary outbound HTTP requests to whatever upstream/URL you submit through it, on your behalf. That's an SSRF surface: only submit requests you intend proxq's configured upstreams to receive.\"\n  shell: \"curl + docker/docker-compose invocations shown in setup.md and this file (container lifecycle, request examples) — no other host access\"\n---\n\n# proxq\n\nThe honey badger of HTTP proxies. POST a request, get a job ID back instantly, come back later for the goods. \"I'll get back to you\" as a service — every HTTP request becomes an async job in a Redis-backed queue (via [asynq](https://github.com/hibiken/asynq)).\n\nFor installation, configuration, and container setup, see [references/setup.md](references/setup.md).\n\n## Security & safety\n\n- **This is an SSRF surface by design.** proxq's whole job is to make outbound HTTP requests to an upstream on your behalf — that's not a bug, it's the feature. Anyone who can submit a job through proxq gets proxq's network position to reach whatever `upstreams[].url` is configured (and, via `directProxyMode`/prefix stripping, whatever path/query you tack onto it). Never point an upstream at internal/admin services you wouldn't otherwise expose, and never let untrusted callers choose the upstream prefix or URL.\n- **No built-in authentication or authorization.** proxq ships with zero auth — no API key, no bearer token, no allowlist. Anyone who can reach `PROXQ_URL` can submit jobs, poll any job ID, and cancel any job ID (job IDs are UUIDv4 but there is no ownership check). Front it with a reverse proxy doing auth (basic auth, mTLS, an API gateway) or bind it to loopback/an internal network only — do not expose a bare proxq instance to the open internet.\n- **Trusted upstreams only.** Configure `upstreams[].url` to point only at backends you control or explicitly trust. proxq forwards the full original request (method, headers, body) plus `X-Forwarded-For`/`X-Real-IP`/`X-Forwarded-Proto` — treat the upstream config the same way you'd treat a reverse-proxy target list.\n- **Consumer-only.** This skill talks to an instance you (or your operator) already run and trust. It never provisions, hardens, or reconfigures the server — that's covered in setup.md as an explicit operator step.\n- **Cancelling a job** — `DELETE /__jobs/{id}` best-effort stops an in-flight job and deletes its record (no undo). Per the no-auth point above there's no ownership check, so only cancel a job you submitted or one the user explicitly named — don't guess IDs or bulk-cancel.\n\n## When To Use\n\n- Put an async facade in front of a slow backend so callers get an instant response instead of hanging on a long-running request.\n- Decouple a client from an upstream that occasionally times out — proxq queues the request, retries transport failures automatically, and the client polls at its own pace.\n- Sit behind a CDN/reverse-proxy with a short request timeout while your real backend takes minutes.\n- Relay webhooks without blocking the sender, with configurable retry/backoff.\n- Queue large uploads or long-running processing jobs (video, exports, reports) so the client doesn't hold a connection open.\n- Mix sync and async traffic on one gateway: fast paths (auth, health) bypass the queue via `pathFilter`, slow paths get queued.\n- Cache idempotent (or even non-idempotent-but-repeatable) responses so duplicate requests don't re-hit the upstream.\n\n## When NOT To Use\n\n- True real-time / streaming responses — the whole model is submit-then-poll; there's no push/webhook-back-to-caller notification built in.\n- WebSocket or chunked-transfer traffic that needs the queue semantics — those bypass the queue automatically and just get reverse-proxied straight through (see `directProxyMode` in setup.md), so don't expect a job ID for them.\n- As a public-facing endpoint without your own auth layer in front — proxq has none.\n- Pointing an upstream at anything you don't fully trust — proxq will happily forward arbitrary methods/bodies/headers to it.\n\n## Usage\n\nPoint at a running instance:\n\n```bash\nexport PROXQ_URL=http://localhost:8080\n```\n\nAll job-management endpoints live under `jobsPath` (default `/__jobs`). Every response proxq itself generates (not proxied from upstream) carries `X-Proxq-Source: proxq` — that's how you distinguish a proxq-origin response (job not ready, no upstream match, proxq error) from a real upstream response replayed verbatim.\n\n### Submit a job\n\nAny request that doesn't hit a job endpoint gets routed by longest-prefix match to a configured upstream and queued (unless it qualifies for [direct-proxy bypass](#direct-proxy-bypass) — see setup.md).\n\n```bash\ncurl -s -X POST \"$PROXQ_URL/api/heavy-computation\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer $UPSTREAM_TOKEN\" \\\n  -d '{\"data\": \"lots of it\"}'\n# 202 Accepted, X-Proxq-Source: proxq\n# {\"jobId\": \"550e8400-e29b-41d4-a716-446655440000\"}\n```\n\nIf no upstream prefix matches the request path: `502 Bad Gateway`, `X-Proxq-Source: proxq`.\n\nOptional per-request override header: `X-Proxq-Timeout: <go-duration>` (e.g. `X-Proxq-Timeout: 30s`) overrides the upstream's configured `timeout` for that one request. Invalid value → `400 Bad Request`.\n\n### Poll job status\n\n```bash\ncurl -s \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\"\n```\n\n```json\n{\"id\": \"550e8400-...\", \"status\": \"completed\", \"completedAt\": \"2025-01-01T00:00:00Z\"}\n```\n\nFailed job includes `error`:\n\n```json\n{\"id\": \"550e8400-...\", \"status\": \"failed\", \"error\": \"forward request: dial tcp: connection refused\"}\n```\n\n`status` is one of `queued` (pending/scheduled/aggregating), `running` (active, or waiting on a retry), `completed` (done — response stored, even if upstream returned 4xx/5xx), `failed` (transport broke and retries are exhausted). Unknown job ID → `404 Not Found`, `X-Proxq-Source: proxq`, body `{\"code\":\"NOT_FOUND\",\"message\":\"Not found\"}`.\n\n### Fetch job content (the payoff)\n\nReplays the upstream response exactly — status code, headers, body — as if you'd called upstream directly.\n\n```bash\ncurl -si \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000/content\"\n```\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/json\nX-Custom-Header: from-upstream\n\n{\"result\": \"done\"}\n```\n\nIf upstream returned a 404, you get 404 back too — but **without** `X-Proxq-Source` (it's a real upstream response). If the job isn't done yet, or doesn't exist: `404 Not Found` **with** `X-Proxq-Source: proxq`. That header is the whole disambiguation trick — present means \"proxq talking\", absent means \"upstream talking\".\n\n### Cancel a job\n\n```bash\ncurl -s -X DELETE \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\"\n# {\"status\": \"cancelled\"}\n```\n\nBest-effort: attempts to stop in-flight processing, then deletes the task record. Unknown job ID → `404 Not Found`, `X-Proxq-Source: proxq`. (See [Security & safety](#security--safety) — no ownership check, so only cancel jobs you own.)\n\n### Poll loop example\n\n```bash\nJOB_ID=$(curl -s -X POST \"$PROXQ_URL/api/report\" -d '{}' | jq -r .jobId)\n\nwhile :; do\n  STATUS=$(curl -s \"$PROXQ_URL/__jobs/$JOB_ID\" | jq -r .status)\n  case \"$STATUS\" in\n    completed) curl -s \"$PROXQ_URL/__jobs/$JOB_ID/content\" | jq; break ;;\n    failed)    echo \"job failed\" >&2; break ;;\n    *)         sleep 2 ;;\n  esac\ndone\n```\n\nFor upstream routing rules (prefix matching/stripping), direct-proxy bypass conditions, caching behavior, and the full config reference (env vars, docker run/compose), see [references/setup.md](references/setup.md).\n\nFile v0.11.4:_meta.json\n\n{\n  \"ownerId\": \"kn79dhvmpjng4rp2jjk8k0v5xx80ccbk\",\n  \"slug\": \"proxq\",\n  \"version\": \"0.11.4\",\n  \"publishedAt\": 1791645929937\n}\n\nFile v0.11.4:references/setup.md\n\n# proxq setup\n\n## Quick start (docker compose)\n\n```yaml\nservices:\n  proxq:\n    image: psyb0t/proxq\n    ports:\n      - \"127.0.0.1:8080:8080\"   # bind loopback-only; no built-in auth (see SKILL.md Security & safety)\n    environment:\n      PROXQ_CONFIG: /etc/proxq/config.yaml\n    configs:\n      - source: proxq_config\n        target: /etc/proxq/config.yaml\n    depends_on:\n      - redis\n\n  redis:\n    image: redis:7-alpine\n    restart: unless-stopped\n\nconfigs:\n  proxq_config:\n    content: |\n      listenAddress: \"0.0.0.0:8080\"\n      redis:\n        addr: \"redis:6379\"\n      upstreams:\n        - prefix: \"/\"\n          url: \"http://your-api:3000\"\n```\n\n```bash\ndocker compose up -d\ncurl http://127.0.0.1:8080/__jobs/nonexistent-id   # 404, X-Proxq-Source: proxq → confirms it's up\n```\n\n## Docker run (config file mounted from host)\n\n```bash\ndocker run -d \\\n  --name proxq \\\n  -p 127.0.0.1:8080:8080 \\\n  -e PROXQ_CONFIG=/etc/proxq/config.yaml \\\n  -v \"$(pwd)/config.yaml:/etc/proxq/config.yaml:ro\" \\\n  --link redis \\\n  psyb0t/proxq\n```\n\nRequires a reachable Redis instance (`redis:7-alpine` or any compatible server).\n\n## Config resolution\n\nConfig path is resolved in this order: `--config` CLI flag → `PROXQ_CONFIG` env var → `config.yaml` in the current directory. Everything else lives inside the YAML file itself — there is no per-field env var override for the rest of the settings, so mount/generate the YAML.\n\n| Env var / flag | Purpose |\n|---|---|\n| `--config <path>` | CLI flag, highest priority |\n| `PROXQ_CONFIG` | Path to the YAML config file, read if `--config` is unset |\n\nThe published image runs the process as a fixed non-root user (`proxq`) baked into the image at build time — there is no `PUID`/`PGID` override.\n\n## Config file reference (YAML)\n\n### Global settings\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `listenAddress` | string | `127.0.0.1:8080` | HTTP server bind address |\n| `redis.addr` | string | `127.0.0.1:6379` | Redis server address |\n| `redis.username` | string | `\"\"` | Redis ACL username. Empty uses the default user |\n| `redis.password` | string | `\"\"` | Redis password |\n| `redis.db` | int | `0` | Redis database number |\n| `queue` | string | `default` | asynq queue name |\n| `concurrency` | int | `10` | Concurrent workers hitting upstream |\n| `jobsPath` | string | `/__jobs` | Base path for the jobs API endpoints |\n| `taskRetention` | duration | `1h` | How long completed/failed jobs stay in Redis before eviction |\n\nDuration values use Go syntax: `30s`, `5m`, `1h`, `1h30m`.\n\n### Upstreams (`upstreams[]`)\n\nRouted by longest path-prefix match; the matched prefix is stripped before forwarding.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `prefix` | string | **required** | URL path prefix for routing. Stripped before forwarding. |\n| `url` | string | **required** | Upstream server URL. May include a path (e.g. `http://api:3000/v2`). |\n| `timeout` | duration | `5m` | Per-upstream request timeout (overridable per-request via `X-Proxq-Timeout`) |\n| `maxRetries` | int | `0` | Retry attempts on transport failure. `0` = no retries. |\n| `retryDelay` | duration | `0` | Fixed delay between retries. `0` = exponential backoff (`n^4` seconds: 1s, 16s, 81s, ~4m, ~10m for attempts 1-5). |\n| `maxBodySize` | int (bytes) | `10485760` (10 MB) | Max request body buffered into the queue |\n| `directProxyThreshold` | int (bytes) | `10485760` (10 MB) | Body size above which requests bypass the queue entirely. `0` disables (always queue regardless of size, up to `maxBodySize`). |\n| `directProxyMode` | string | `proxy` | How bypassed requests reach upstream: `proxy` (reverse-proxied, client never sees upstream URL) or `redirect` (`307 Temporary Redirect` to the upstream URL) |\n| `cacheKeyExcludeHeaders` | list[string] | `[]` (defaults apply) | Headers excluded from the cache key. Empty list = built-in defaults (`X-Request-ID`, `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`). Setting this **replaces** the defaults entirely. |\n| `pathFilter.mode` | string | `blacklist` | `blacklist`: matching paths bypass the queue. `whitelist`: only matching paths get queued. |\n| `pathFilter.patterns` | list[string] | `[]` | Regex patterns matched against the request path. |\n\n### Cache (`cache`)\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `cache.mode` | string | `none` | `none`, `memory` (in-process LRU), or `redis` (shared, same Redis instance as the queue) |\n| `cache.ttl` | duration | `5m` | Freshness window |\n| `cache.maxEntries` | int | `10000` | Max entries for in-memory LRU mode |\n| `cache.redisKeyPrefix` | string | `proxq:` | Key prefix for Redis cache mode, avoids colliding with job data |\n\nCache rules: any HTTP method can be cached (same body → cache hit, different body → miss); only 2xx upstream responses are cached; cache key = `sha256(method + url + headers + body)` with volatile headers excluded per `cacheKeyExcludeHeaders`. Responses carry `X-Cache-Status: HIT` or `MISS`.\n\n### Direct proxy bypass — checked in this order\n\n| Condition | Why |\n|---|---|\n| WebSocket (`Connection: upgrade` + `Upgrade: websocket`) | Persistent bidirectional, can't queue |\n| Path filter match (per-upstream `pathFilter`) | Explicit opt-out |\n| Chunked transfer (`Transfer-Encoding: chunked`) | Size unknown |\n| Body over `directProxyThreshold` | Avoid buffering huge uploads into Redis |\n\n### Validation (fails startup if violated)\n\n- At least one upstream is required.\n- Every upstream needs both `prefix` and `url`.\n- Single upstream may use `prefix: \"/\"` (catch-all). Multiple upstreams may **not** include `prefix: \"/\"` — too ambiguous.\n- No nested prefixes (`/api` and `/api/v2` together is an error).\n- No upstream prefix may conflict with `jobsPath` (e.g. an upstream at `/__jobs` when `jobsPath` is `/__jobs`).\n- All `pathFilter.patterns` must be valid regexes.\n\n### Example config\n\n```yaml\nlistenAddress: \"0.0.0.0:8080\"\n\nredis:\n  addr: \"redis:6379\"\n  username: \"\"\n  password: \"\"\n  db: 0\n\nqueue: \"default\"\nconcurrency: 10\njobsPath: \"/__jobs\"\ntaskRetention: \"1h\"\n\ncache:\n  mode: \"redis\"\n  ttl: \"10m\"\n  redisKeyPrefix: \"proxq:\"\n\nupstreams:\n  - prefix: \"/api\"\n    url: \"http://api-server:3000\"\n    timeout: \"5m\"\n    maxRetries: 3\n    retryDelay: \"10s\"\n    pathFilter:\n      mode: \"blacklist\"\n      patterns:\n        - \"^/api/auth\"\n        - \"^/api/health\"\n\n  - prefix: \"/uploads\"\n    url: \"http://file-server:9000/storage\"\n    timeout: \"10m\"\n    maxBodySize: 1073741824\n    directProxyThreshold: 0\n    directProxyMode: \"redirect\"\n```\n\n## Headers reference\n\nSet by proxq on responses it generates itself (never on responses proxied verbatim from upstream):\n\n| Header | Value | When |\n|---|---|---|\n| `X-Proxq-Source` | `proxq` | `202` accepted, `502` no upstream match, `500` internal errors, `307` redirects, `404` from job endpoints, reverse-proxy errors |\n| `X-Cache-Status` | `HIT` / `MISS` | On cached responses, when caching is enabled |\n\nAccepted from the client:\n\n| Header | Effect |\n|---|---|\n| `X-Proxq-Timeout` | Go duration string (e.g. `30s`), overrides the matched upstream's `timeout` for that one submitted request. Invalid value → `400`. |\n\nForwarded to upstream on every proxied/queued request: original headers as-is, plus `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`. Hop-by-hop headers (`Connection`, `Keep-Alive`, `Proxy-Authenticate`, `Proxy-Authorization`, `TE`, `Trailers`, `Transfer-Encoding`, `Upgrade`) are stripped per RFC 7230.\n\n## No built-in auth\n\nproxq has no authentication of its own — no API key, bearer token, or allowlist on any endpoint (job submission, status, content, cancel). Put it behind:\n- A reverse proxy doing auth (basic auth, OAuth2 proxy, mTLS) in front of `listenAddress`.\n- Network isolation — bind `listenAddress`/the published port to loopback or an internal-only network, never `0.0.0.0` on a publicly routable host without a fronting proxy.\n\nJob IDs are UUIDv4 (unguessable in practice) but there is **no ownership check** — anyone who can reach the instance and knows/guesses a job ID can poll or cancel it.\n\n## Management / development (operator-side, from the repo)\n\n```bash\nmake dep            # vendor dependencies\nmake lint           # golangci-lint\nmake test           # unit + integration tests (race detector on)\nmake test-coverage  # tests with 90% coverage threshold\nmake build           # docker build\n\n# e2e tests — spins up Redis + upstream + proxq via testcontainers\ncd tests && go test -v -timeout 10m ./...\n```\n\nFile v0.11.4:skill-card.md\n\n## Description:\n\nHelps developers submit HTTP requests to a trusted proxq instance for asynchronous forwarding, then poll results or cancel jobs.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[psyb0t](https://clawhub.ai/user/psyb0t)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and engineers use this skill to submit requests to a proxq instance, monitor queued work, retrieve upstream responses, and cancel their own jobs when needed.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: An internet-exposed proxq instance has no built-in authentication or job ownership checks.\n\nMitigation: Bind it to loopback or an internal network, require authentication or mTLS at the edge, and cancel only jobs you own.\n\nRisk: Submitted requests can reach configured upstreams from the proxy's network position.\n\nMitigation: Configure only trusted upstreams and submit only requests intended for those services.\n\nRisk: Unpinned container images can change between deployments.\n\nMitigation: Pin Docker images before production use.\n\n## Reference(s):\n\n- [proxq ClawHub release](https://clawhub.ai/psyb0t/skills/proxq)\n- [proxq setup and configuration](references/setup.md)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration guidance, Text]\n\n**Output Format:** [Markdown with shell and YAML examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Job IDs, status, and upstream responses are returned by the configured proxq instance.]\n\n## Skill Version(s):\n\n0.11.4 (source: ClawHub release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v0.11.1: 4 files, 9296 bytes\n\nFiles: references/setup.md (8515b), skill-card.md (2035b), SKILL.md (8743b), _meta.json (125b)\n\nFile v0.11.1:SKILL.md\n\n---\nname: proxq\ndescription: Go, Redis-backed async HTTP proxy queue (built on asynq). POST any HTTP request (any method, any path, any body) to a configured upstream, get a job ID back instantly (202), a worker forwards it later, you poll GET /__jobs/{id} for status (queued/running/completed/failed) and GET /__jobs/{id}/content for the replayed upstream response (status/headers/body). DELETE /__jobs/{id} cancels. Path-prefix routing to multiple upstreams, per-upstream timeout/retries/pathFilter, optional response caching (memory or Redis LRU), automatic direct-proxy bypass for WebSocket/chunked/large-body requests. No built-in auth. Use when the user wants to turn a slow/unreliable backend into a fire-and-forget async API, decouple a client from upstream latency, relay webhooks with retries, or queue heavy uploads/processing jobs behind short-timeout reverse proxies.\nhomepage: https://github.com/psyb0t/docker-proxq\nuser-invocable: true\nmetadata:\n  { \"openclaw\": { \"emoji\": \"🦡\", \"primaryEnv\": \"PROXQ_URL\", \"requires\": { \"bins\": [\"curl\", \"docker\"] } } }\npermissions:\n  network: \"outbound HTTP to the configured PROXQ_URL (submit/poll/cancel job calls) — AND proxq itself makes arbitrary outbound HTTP requests to whatever upstream/URL you submit through it, on your behalf. That's an SSRF surface: only submit requests you intend proxq's configured upstreams to receive.\"\n  shell: \"curl + docker/docker-compose invocations shown in setup.md and this file (container lifecycle, request examples) — no other host access\"\n---\n\n# proxq\n\nThe honey badger of HTTP proxies. POST a request, get a job ID back instantly, come back later for the goods. \"I'll get back to you\" as a service — every HTTP request becomes an async job in a Redis-backed queue (via [asynq](https://github.com/hibiken/asynq)).\n\nFor installation, configuration, and container setup, see [references/setup.md](references/setup.md).\n\n## Security & safety\n\n- **This is an SSRF surface by design.** proxq's whole job is to make outbound HTTP requests to an upstream on your behalf — that's not a bug, it's the feature. Anyone who can submit a job through proxq gets proxq's network position to reach whatever `upstreams[].url` is configured (and, via `directProxyMode`/prefix stripping, whatever path/query you tack onto it). Never point an upstream at internal/admin services you wouldn't otherwise expose, and never let untrusted callers choose the upstream prefix or URL.\n- **No built-in authentication or authorization.** proxq ships with zero auth — no API key, no bearer token, no allowlist. Anyone who can reach `PROXQ_URL` can submit jobs, poll any job ID, and cancel any job ID (job IDs are UUIDv4 but there is no ownership check). Front it with a reverse proxy doing auth (basic auth, mTLS, an API gateway) or bind it to loopback/an internal network only — do not expose a bare proxq instance to the open internet.\n- **Trusted upstreams only.** Configure `upstreams[].url` to point only at backends you control or explicitly trust. proxq forwards the full original request (method, headers, body) plus `X-Forwarded-For`/`X-Real-IP`/`X-Forwarded-Proto` — treat the upstream config the same way you'd treat a reverse-proxy target list.\n- **Consumer-only.** This skill talks to an instance you (or your operator) already run and trust. It never provisions, hardens, or reconfigures the server — that's covered in setup.md as an explicit operator step.\n- **Cancelling a job** — `DELETE /__jobs/{id}` best-effort stops an in-flight job and deletes its record (no undo). Per the no-auth point above there's no ownership check, so only cancel a job you submitted or one the user explicitly named — don't guess IDs or bulk-cancel.\n\n## When To Use\n\n- Put an async facade in front of a slow backend so callers get an instant response instead of hanging on a long-running request.\n- Decouple a client from an upstream that occasionally times out — proxq queues the request, retries transport failures automatically, and the client polls at its own pace.\n- Sit behind a CDN/reverse-proxy with a short request timeout while your real backend takes minutes.\n- Relay webhooks without blocking the sender, with configurable retry/backoff.\n- Queue large uploads or long-running processing jobs (video, exports, reports) so the client doesn't hold a connection open.\n- Mix sync and async traffic on one gateway: fast paths (auth, health) bypass the queue via `pathFilter`, slow paths get queued.\n- Cache idempotent (or even non-idempotent-but-repeatable) responses so duplicate requests don't re-hit the upstream.\n\n## When NOT To Use\n\n- True real-time / streaming responses — the whole model is submit-then-poll; there's no push/webhook-back-to-caller notification built in.\n- WebSocket or chunked-transfer traffic that needs the queue semantics — those bypass the queue automatically and just get reverse-proxied straight through (see `directProxyMode` in setup.md), so don't expect a job ID for them.\n- As a public-facing endpoint without your own auth layer in front — proxq has none.\n- Pointing an upstream at anything you don't fully trust — proxq will happily forward arbitrary methods/bodies/headers to it.\n\n## Usage\n\nPoint at a running instance:\n\n```bash\nexport PROXQ_URL=http://localhost:8080\n```\n\nAll job-management endpoints live under `jobsPath` (default `/__jobs`). Every response proxq itself generates (not proxied from upstream) carries `X-Proxq-Source: proxq` — that's how you distinguish a proxq-origin response (job not ready, no upstream match, proxq error) from a real upstream response replayed verbatim.\n\n### Submit a job\n\nAny request that doesn't hit a job endpoint gets routed by longest-prefix match to a configured upstream and queued (unless it qualifies for [direct-proxy bypass](#direct-proxy-bypass) — see setup.md).\n\n```bash\ncurl -s -X POST \"$PROXQ_URL/api/heavy-computation\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer $UPSTREAM_TOKEN\" \\\n  -d '{\"data\": \"lots of it\"}'\n# 202 Accepted, X-Proxq-Source: proxq\n# {\"jobId\": \"550e8400-e29b-41d4-a716-446655440000\"}\n```\n\nIf no upstream prefix matches the request path: `502 Bad Gateway`, `X-Proxq-Source: proxq`.\n\nOptional per-request override header: `X-Proxq-Timeout: <go-duration>` (e.g. `X-Proxq-Timeout: 30s`) overrides the upstream's configured `timeout` for that one request. Invalid value → `400 Bad Request`.\n\n### Poll job status\n\n```bash\ncurl -s \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\"\n```\n\n```json\n{\"id\": \"550e8400-...\", \"status\": \"completed\", \"completedAt\": \"2025-01-01T00:00:00Z\"}\n```\n\nFailed job includes `error`:\n\n```json\n{\"id\": \"550e8400-...\", \"status\": \"failed\", \"error\": \"forward request: dial tcp: connection refused\"}\n```\n\n`status` is one of `queued` (pending/scheduled/aggregating), `running` (active, or waiting on a retry), `completed` (done — response stored, even if upstream returned 4xx/5xx), `failed` (transport broke and retries are exhausted). Unknown job ID → `404 Not Found`, `X-Proxq-Source: proxq`, body `{\"code\":\"NOT_FOUND\",\"message\":\"Not found\"}`.\n\n### Fetch job content (the payoff)\n\nReplays the upstream response exactly — status code, headers, body — as if you'd called upstream directly.\n\n```bash\ncurl -si \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000/content\"\n```\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/json\nX-Custom-Header: from-upstream\n\n{\"result\": \"done\"}\n```\n\nIf upstream returned a 404, you get 404 back too — but **without** `X-Proxq-Source` (it's a real upstream response). If the job isn't done yet, or doesn't exist: `404 Not Found` **with** `X-Proxq-Source: proxq`. That header is the whole disambiguation trick — present means \"proxq talking\", absent means \"upstream talking\".\n\n### Cancel a job\n\n```bash\ncurl -s -X DELETE \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\"\n# {\"status\": \"cancelled\"}\n```\n\nBest-effort: attempts to stop in-flight processing, then deletes the task record. Unknown job ID → `404 Not Found`, `X-Proxq-Source: proxq`. (See [Security & safety](#security--safety) — no ownership check, so only cancel jobs you own.)\n\n### Poll loop example\n\n```bash\nJOB_ID=$(curl -s -X POST \"$PROXQ_URL/api/report\" -d '{}' | jq -r .jobId)\n\nwhile :; do\n  STATUS=$(curl -s \"$PROXQ_URL/__jobs/$JOB_ID\" | jq -r .status)\n  case \"$STATUS\" in\n    completed) curl -s \"$PROXQ_URL/__jobs/$JOB_ID/content\" | jq; break ;;\n    failed)    echo \"job failed\" >&2; break ;;\n    *)         sleep 2 ;;\n  esac\ndone\n```\n\nFor upstream routing rules (prefix matching/stripping), direct-proxy bypass conditions, caching behavior, and the full config reference (env vars, docker run/compose), see [references/setup.md](references/setup.md).\n\nFile v0.11.1:_meta.json\n\n{\n  \"ownerId\": \"kn79dhvmpjng4rp2jjk8k0v5xx80ccbk\",\n  \"slug\": \"proxq\",\n  \"version\": \"0.11.1\",\n  \"publishedAt\": 1790389922876\n}\n\nFile v0.11.1:references/setup.md\n\n# proxq setup\n\n## Quick start (docker compose)\n\n```yaml\nservices:\n  proxq:\n    image: psyb0t/proxq\n    ports:\n      - \"127.0.0.1:8080:8080\"   # bind loopback-only; no built-in auth (see SKILL.md Security & safety)\n    environment:\n      PROXQ_CONFIG: /etc/proxq/config.yaml\n    configs:\n      - source: proxq_config\n        target: /etc/proxq/config.yaml\n    depends_on:\n      - redis\n\n  redis:\n    image: redis:7-alpine\n    restart: unless-stopped\n\nconfigs:\n  proxq_config:\n    content: |\n      listenAddress: \"0.0.0.0:8080\"\n      redis:\n        addr: \"redis:6379\"\n      upstreams:\n        - prefix: \"/\"\n          url: \"http://your-api:3000\"\n```\n\n```bash\ndocker compose up -d\ncurl http://127.0.0.1:8080/__jobs/nonexistent-id   # 404, X-Proxq-Source: proxq → confirms it's up\n```\n\n## Docker run (config file mounted from host)\n\n```bash\ndocker run -d \\\n  --name proxq \\\n  -p 127.0.0.1:8080:8080 \\\n  -e PROXQ_CONFIG=/etc/proxq/config.yaml \\\n  -v \"$(pwd)/config.yaml:/etc/proxq/config.yaml:ro\" \\\n  --link redis \\\n  psyb0t/proxq\n```\n\nRequires a reachable Redis instance (`redis:7-alpine` or any compatible server).\n\n## Config resolution\n\nConfig path is resolved in this order: `--config` CLI flag → `PROXQ_CONFIG` env var → `config.yaml` in the current directory. Everything else lives inside the YAML file itself — there is no per-field env var override for the rest of the settings, so mount/generate the YAML.\n\n| Env var / flag | Purpose |\n|---|---|\n| `--config <path>` | CLI flag, highest priority |\n| `PROXQ_CONFIG` | Path to the YAML config file, read if `--config` is unset |\n\nThe published image runs the process as a fixed non-root user (`proxq`) baked into the image at build time — there is no `PUID`/`PGID` override.\n\n## Config file reference (YAML)\n\n### Global settings\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `listenAddress` | string | `127.0.0.1:8080` | HTTP server bind address |\n| `redis.addr` | string | `127.0.0.1:6379` | Redis server address |\n| `redis.username` | string | `\"\"` | Redis ACL username. Empty uses the default user |\n| `redis.password` | string | `\"\"` | Redis password |\n| `redis.db` | int | `0` | Redis database number |\n| `queue` | string | `default` | asynq queue name |\n| `concurrency` | int | `10` | Concurrent workers hitting upstream |\n| `jobsPath` | string | `/__jobs` | Base path for the jobs API endpoints |\n| `taskRetention` | duration | `1h` | How long completed/failed jobs stay in Redis before eviction |\n\nDuration values use Go syntax: `30s`, `5m`, `1h`, `1h30m`.\n\n### Upstreams (`upstreams[]`)\n\nRouted by longest path-prefix match; the matched prefix is stripped before forwarding.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `prefix` | string | **required** | URL path prefix for routing. Stripped before forwarding. |\n| `url` | string | **required** | Upstream server URL. May include a path (e.g. `http://api:3000/v2`). |\n| `timeout` | duration | `5m` | Per-upstream request timeout (overridable per-request via `X-Proxq-Timeout`) |\n| `maxRetries` | int | `0` | Retry attempts on transport failure. `0` = no retries. |\n| `retryDelay` | duration | `0` | Fixed delay between retries. `0` = exponential backoff (`n^4` seconds: 1s, 16s, 81s, ~4m, ~10m for attempts 1-5). |\n| `maxBodySize` | int (bytes) | `10485760` (10 MB) | Max request body buffered into the queue |\n| `directProxyThreshold` | int (bytes) | `10485760` (10 MB) | Body size above which requests bypass the queue entirely. `0` disables (always queue regardless of size, up to `maxBodySize`). |\n| `directProxyMode` | string | `proxy` | How bypassed requests reach upstream: `proxy` (reverse-proxied, client never sees upstream URL) or `redirect` (`307 Temporary Redirect` to the upstream URL) |\n| `cacheKeyExcludeHeaders` | list[string] | `[]` (defaults apply) | Headers excluded from the cache key. Empty list = built-in defaults (`X-Request-ID`, `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`). Setting this **replaces** the defaults entirely. |\n| `pathFilter.mode` | string | `blacklist` | `blacklist`: matching paths bypass the queue. `whitelist`: only matching paths get queued. |\n| `pathFilter.patterns` | list[string] | `[]` | Regex patterns matched against the request path. |\n\n### Cache (`cache`)\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `cache.mode` | string | `none` | `none`, `memory` (in-process LRU), or `redis` (shared, same Redis instance as the queue) |\n| `cache.ttl` | duration | `5m` | Freshness window |\n| `cache.maxEntries` | int | `10000` | Max entries for in-memory LRU mode |\n| `cache.redisKeyPrefix` | string | `proxq:` | Key prefix for Redis cache mode, avoids colliding with job data |\n\nCache rules: any HTTP method can be cached (same body → cache hit, different body → miss); only 2xx upstream responses are cached; cache key = `sha256(method + url + headers + body)` with volatile headers excluded per `cacheKeyExcludeHeaders`. Responses carry `X-Cache-Status: HIT` or `MISS`.\n\n### Direct proxy bypass — checked in this order\n\n| Condition | Why |\n|---|---|\n| WebSocket (`Connection: upgrade` + `Upgrade: websocket`) | Persistent bidirectional, can't queue |\n| Path filter match (per-upstream `pathFilter`) | Explicit opt-out |\n| Chunked transfer (`Transfer-Encoding: chunked`) | Size unknown |\n| Body over `directProxyThreshold` | Avoid buffering huge uploads into Redis |\n\n### Validation (fails startup if violated)\n\n- At least one upstream is required.\n- Every upstream needs both `prefix` and `url`.\n- Single upstream may use `prefix: \"/\"` (catch-all). Multiple upstreams may **not** include `prefix: \"/\"` — too ambiguous.\n- No nested prefixes (`/api` and `/api/v2` together is an error).\n- No upstream prefix may conflict with `jobsPath` (e.g. an upstream at `/__jobs` when `jobsPath` is `/__jobs`).\n- All `pathFilter.patterns` must be valid regexes.\n\n### Example config\n\n```yaml\nlistenAddress: \"0.0.0.0:8080\"\n\nredis:\n  addr: \"redis:6379\"\n  username: \"\"\n  password: \"\"\n  db: 0\n\nqueue: \"default\"\nconcurrency: 10\njobsPath: \"/__jobs\"\ntaskRetention: \"1h\"\n\ncache:\n  mode: \"redis\"\n  ttl: \"10m\"\n  redisKeyPrefix: \"proxq:\"\n\nupstreams:\n  - prefix: \"/api\"\n    url: \"http://api-server:3000\"\n    timeout: \"5m\"\n    maxRetries: 3\n    retryDelay: \"10s\"\n    pathFilter:\n      mode: \"blacklist\"\n      patterns:\n        - \"^/api/auth\"\n        - \"^/api/health\"\n\n  - prefix: \"/uploads\"\n    url: \"http://file-server:9000/storage\"\n    timeout: \"10m\"\n    maxBodySize: 1073741824\n    directProxyThreshold: 0\n    directProxyMode: \"redirect\"\n```\n\n## Headers reference\n\nSet by proxq on responses it generates itself (never on responses proxied verbatim from upstream):\n\n| Header | Value | When |\n|---|---|---|\n| `X-Proxq-Source` | `proxq` | `202` accepted, `502` no upstream match, `500` internal errors, `307` redirects, `404` from job endpoints, reverse-proxy errors |\n| `X-Cache-Status` | `HIT` / `MISS` | On cached responses, when caching is enabled |\n\nAccepted from the client:\n\n| Header | Effect |\n|---|---|\n| `X-Proxq-Timeout` | Go duration string (e.g. `30s`), overrides the matched upstream's `timeout` for that one submitted request. Invalid value → `400`. |\n\nForwarded to upstream on every proxied/queued request: original headers as-is, plus `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`. Hop-by-hop headers (`Connection`, `Keep-Alive`, `Proxy-Authenticate`, `Proxy-Authorization`, `TE`, `Trailers`, `Transfer-Encoding`, `Upgrade`) are stripped per RFC 7230.\n\n## No built-in auth\n\nproxq has no authentication of its own — no API key, bearer token, or allowlist on any endpoint (job submission, status, content, cancel). Put it behind:\n- A reverse proxy doing auth (basic auth, OAuth2 proxy, mTLS) in front of `listenAddress`.\n- Network isolation — bind `listenAddress`/the published port to loopback or an internal-only network, never `0.0.0.0` on a publicly routable host without a fronting proxy.\n\nJob IDs are UUIDv4 (unguessable in practice) but there is **no ownership check** — anyone who can reach the instance and knows/guesses a job ID can poll or cancel it.\n\n## Management / development (operator-side, from the repo)\n\n```bash\nmake dep            # vendor dependencies\nmake lint           # golangci-lint\nmake test           # unit + integration tests (race detector on)\nmake test-coverage  # tests with 90% coverage threshold\nmake build           # docker build\n\n# e2e tests — spins up Redis + upstream + proxq via testcontainers\ncd tests && go test -v -timeout 10m ./...\n```\n\nFile v0.11.1:skill-card.md\n\n## Description:\n\nGuides agents in submitting HTTP requests to a Redis-backed proxy queue, polling for results, and managing queued jobs.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[psyb0t](https://clawhub.ai/user/psyb0t)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and operators use this skill to send requests through an existing proxq instance, poll for delayed responses, and cancel their own jobs when needed.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Unauthenticated access can allow anyone who reaches proxq to submit, view, or cancel jobs.\n\nMitigation: Use authentication or network isolation; never expose the instance directly to the internet.\n\nRisk: Proxq can forward requests from its network position to configured upstreams, including sensitive internal services.\n\nMitigation: Use only trusted upstreams, avoid internal or administrative targets, and restrict who can submit requests.\n\nRisk: Unpinned container images can change unexpectedly between deployments.\n\nMitigation: Pin Docker image versions or digests before production use.\n\n## Reference(s):\n\n- [proxq on ClawHub](https://clawhub.ai/psyb0t/skills/proxq)\n- [proxq project homepage](https://github.com/psyb0t/docker-proxq)\n- [proxq setup and configuration](references/setup.md)\n- [asynq](https://github.com/hibiken/asynq)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration guidance, API request guidance]\n\n**Output Format:** [Markdown with shell commands and YAML examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Job IDs, status responses, and replayed HTTP response guidance when applicable]\n\n## Skill Version(s):\n\n0.11.1 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v0.10.13: 4 files, 9488 bytes\n\nFiles: references/setup.md (8413b), skill-card.md (2497b), SKILL.md (8743b), _meta.json (126b)\n\nFile v0.10.13:SKILL.md\n\n---\nname: proxq\ndescription: Go, Redis-backed async HTTP proxy queue (built on asynq). POST any HTTP request (any method, any path, any body) to a configured upstream, get a job ID back instantly (202), a worker forwards it later, you poll GET /__jobs/{id} for status (queued/running/completed/failed) and GET /__jobs/{id}/content for the replayed upstream response (status/headers/body). DELETE /__jobs/{id} cancels. Path-prefix routing to multiple upstreams, per-upstream timeout/retries/pathFilter, optional response caching (memory or Redis LRU), automatic direct-proxy bypass for WebSocket/chunked/large-body requests. No built-in auth. Use when the user wants to turn a slow/unreliable backend into a fire-and-forget async API, decouple a client from upstream latency, relay webhooks with retries, or queue heavy uploads/processing jobs behind short-timeout reverse proxies.\nhomepage: https://github.com/psyb0t/docker-proxq\nuser-invocable: true\nmetadata:\n  { \"openclaw\": { \"emoji\": \"🦡\", \"primaryEnv\": \"PROXQ_URL\", \"requires\": { \"bins\": [\"curl\", \"docker\"] } } }\npermissions:\n  network: \"outbound HTTP to the configured PROXQ_URL (submit/poll/cancel job calls) — AND proxq itself makes arbitrary outbound HTTP requests to whatever upstream/URL you submit through it, on your behalf. That's an SSRF surface: only submit requests you intend proxq's configured upstreams to receive.\"\n  shell: \"curl + docker/docker-compose invocations shown in setup.md and this file (container lifecycle, request examples) — no other host access\"\n---\n\n# proxq\n\nThe honey badger of HTTP proxies. POST a request, get a job ID back instantly, come back later for the goods. \"I'll get back to you\" as a service — every HTTP request becomes an async job in a Redis-backed queue (via [asynq](https://github.com/hibiken/asynq)).\n\nFor installation, configuration, and container setup, see [references/setup.md](references/setup.md).\n\n## Security & safety\n\n- **This is an SSRF surface by design.** proxq's whole job is to make outbound HTTP requests to an upstream on your behalf — that's not a bug, it's the feature. Anyone who can submit a job through proxq gets proxq's network position to reach whatever `upstreams[].url` is configured (and, via `directProxyMode`/prefix stripping, whatever path/query you tack onto it). Never point an upstream at internal/admin services you wouldn't otherwise expose, and never let untrusted callers choose the upstream prefix or URL.\n- **No built-in authentication or authorization.** proxq ships with zero auth — no API key, no bearer token, no allowlist. Anyone who can reach `PROXQ_URL` can submit jobs, poll any job ID, and cancel any job ID (job IDs are UUIDv4 but there is no ownership check). Front it with a reverse proxy doing auth (basic auth, mTLS, an API gateway) or bind it to loopback/an internal network only — do not expose a bare proxq instance to the open internet.\n- **Trusted upstreams only.** Configure `upstreams[].url` to point only at backends you control or explicitly trust. proxq forwards the full original request (method, headers, body) plus `X-Forwarded-For`/`X-Real-IP`/`X-Forwarded-Proto` — treat the upstream config the same way you'd treat a reverse-proxy target list.\n- **Consumer-only.** This skill talks to an instance you (or your operator) already run and trust. It never provisions, hardens, or reconfigures the server — that's covered in setup.md as an explicit operator step.\n- **Cancelling a job** — `DELETE /__jobs/{id}` best-effort stops an in-flight job and deletes its record (no undo). Per the no-auth point above there's no ownership check, so only cancel a job you submitted or one the user explicitly named — don't guess IDs or bulk-cancel.\n\n## When To Use\n\n- Put an async facade in front of a slow backend so callers get an instant response instead of hanging on a long-running request.\n- Decouple a client from an upstream that occasionally times out — proxq queues the request, retries transport failures automatically, and the client polls at its own pace.\n- Sit behind a CDN/reverse-proxy with a short request timeout while your real backend takes minutes.\n- Relay webhooks without blocking the sender, with configurable retry/backoff.\n- Queue large uploads or long-running processing jobs (video, exports, reports) so the client doesn't hold a connection open.\n- Mix sync and async traffic on one gateway: fast paths (auth, health) bypass the queue via `pathFilter`, slow paths get queued.\n- Cache idempotent (or even non-idempotent-but-repeatable) responses so duplicate requests don't re-hit the upstream.\n\n## When NOT To Use\n\n- True real-time / streaming responses — the whole model is submit-then-poll; there's no push/webhook-back-to-caller notification built in.\n- WebSocket or chunked-transfer traffic that needs the queue semantics — those bypass the queue automatically and just get reverse-proxied straight through (see `directProxyMode` in setup.md), so don't expect a job ID for them.\n- As a public-facing endpoint without your own auth layer in front — proxq has none.\n- Pointing an upstream at anything you don't fully trust — proxq will happily forward arbitrary methods/bodies/headers to it.\n\n## Usage\n\nPoint at a running instance:\n\n```bash\nexport PROXQ_URL=http://localhost:8080\n```\n\nAll job-management endpoints live under `jobsPath` (default `/__jobs`). Every response proxq itself generates (not proxied from upstream) carries `X-Proxq-Source: proxq` — that's how you distinguish a proxq-origin response (job not ready, no upstream match, proxq error) from a real upstream response replayed verbatim.\n\n### Submit a job\n\nAny request that doesn't hit a job endpoint gets routed by longest-prefix match to a configured upstream and queued (unless it qualifies for [direct-proxy bypass](#direct-proxy-bypass) — see setup.md).\n\n```bash\ncurl -s -X POST \"$PROXQ_URL/api/heavy-computation\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer $UPSTREAM_TOKEN\" \\\n  -d '{\"data\": \"lots of it\"}'\n# 202 Accepted, X-Proxq-Source: proxq\n# {\"jobId\": \"550e8400-e29b-41d4-a716-446655440000\"}\n```\n\nIf no upstream prefix matches the request path: `502 Bad Gateway`, `X-Proxq-Source: proxq`.\n\nOptional per-request override header: `X-Proxq-Timeout: <go-duration>` (e.g. `X-Proxq-Timeout: 30s`) overrides the upstream's configured `timeout` for that one request. Invalid value → `400 Bad Request`.\n\n### Poll job status\n\n```bash\ncurl -s \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\"\n```\n\n```json\n{\"id\": \"550e8400-...\", \"status\": \"completed\", \"completedAt\": \"2025-01-01T00:00:00Z\"}\n```\n\nFailed job includes `error`:\n\n```json\n{\"id\": \"550e8400-...\", \"status\": \"failed\", \"error\": \"forward request: dial tcp: connection refused\"}\n```\n\n`status` is one of `queued` (pending/scheduled/aggregating), `running` (active, or waiting on a retry), `completed` (done — response stored, even if upstream returned 4xx/5xx), `failed` (transport broke and retries are exhausted). Unknown job ID → `404 Not Found`, `X-Proxq-Source: proxq`, body `{\"code\":\"NOT_FOUND\",\"message\":\"Not found\"}`.\n\n### Fetch job content (the payoff)\n\nReplays the upstream response exactly — status code, headers, body — as if you'd called upstream directly.\n\n```bash\ncurl -si \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000/content\"\n```\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/json\nX-Custom-Header: from-upstream\n\n{\"result\": \"done\"}\n```\n\nIf upstream returned a 404, you get 404 back too — but **without** `X-Proxq-Source` (it's a real upstream response). If the job isn't done yet, or doesn't exist: `404 Not Found` **with** `X-Proxq-Source: proxq`. That header is the whole disambiguation trick — present means \"proxq talking\", absent means \"upstream talking\".\n\n### Cancel a job\n\n```bash\ncurl -s -X DELETE \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\"\n# {\"status\": \"cancelled\"}\n```\n\nBest-effort: attempts to stop in-flight processing, then deletes the task record. Unknown job ID → `404 Not Found`, `X-Proxq-Source: proxq`. (See [Security & safety](#security--safety) — no ownership check, so only cancel jobs you own.)\n\n### Poll loop example\n\n```bash\nJOB_ID=$(curl -s -X POST \"$PROXQ_URL/api/report\" -d '{}' | jq -r .jobId)\n\nwhile :; do\n  STATUS=$(curl -s \"$PROXQ_URL/__jobs/$JOB_ID\" | jq -r .status)\n  case \"$STATUS\" in\n    completed) curl -s \"$PROXQ_URL/__jobs/$JOB_ID/content\" | jq; break ;;\n    failed)    echo \"job failed\" >&2; break ;;\n    *)         sleep 2 ;;\n  esac\ndone\n```\n\nFor upstream routing rules (prefix matching/stripping), direct-proxy bypass conditions, caching behavior, and the full config reference (env vars, docker run/compose), see [references/setup.md](references/setup.md).\n\nFile v0.10.13:_meta.json\n\n{\n  \"ownerId\": \"kn79dhvmpjng4rp2jjk8k0v5xx80ccbk\",\n  \"slug\": \"proxq\",\n  \"version\": \"0.10.13\",\n  \"publishedAt\": 1786224920533\n}\n\nFile v0.10.13:references/setup.md\n\n# proxq setup\n\n## Quick start (docker compose)\n\n```yaml\nservices:\n  proxq:\n    image: psyb0t/proxq\n    ports:\n      - \"127.0.0.1:8080:8080\"   # bind loopback-only; no built-in auth (see SKILL.md Security & safety)\n    environment:\n      PROXQ_CONFIG: /etc/proxq/config.yaml\n    configs:\n      - source: proxq_config\n        target: /etc/proxq/config.yaml\n    depends_on:\n      - redis\n\n  redis:\n    image: redis:7-alpine\n    restart: unless-stopped\n\nconfigs:\n  proxq_config:\n    content: |\n      listenAddress: \"0.0.0.0:8080\"\n      redis:\n        addr: \"redis:6379\"\n      upstreams:\n        - prefix: \"/\"\n          url: \"http://your-api:3000\"\n```\n\n```bash\ndocker compose up -d\ncurl http://127.0.0.1:8080/__jobs/nonexistent-id   # 404, X-Proxq-Source: proxq → confirms it's up\n```\n\n## Docker run (config file mounted from host)\n\n```bash\ndocker run -d \\\n  --name proxq \\\n  -p 127.0.0.1:8080:8080 \\\n  -e PROXQ_CONFIG=/etc/proxq/config.yaml \\\n  -v \"$(pwd)/config.yaml:/etc/proxq/config.yaml:ro\" \\\n  --link redis \\\n  psyb0t/proxq\n```\n\nRequires a reachable Redis instance (`redis:7-alpine` or any compatible server).\n\n## Config resolution\n\nConfig path is resolved in this order: `--config` CLI flag → `PROXQ_CONFIG` env var → `config.yaml` in the current directory. Everything else lives inside the YAML file itself — there is no per-field env var override for the rest of the settings, so mount/generate the YAML.\n\n| Env var / flag | Purpose |\n|---|---|\n| `--config <path>` | CLI flag, highest priority |\n| `PROXQ_CONFIG` | Path to the YAML config file, read if `--config` is unset |\n\nThe published image runs the process as a fixed non-root user (`proxq`) baked into the image at build time — there is no `PUID`/`PGID` override.\n\n## Config file reference (YAML)\n\n### Global settings\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `listenAddress` | string | `127.0.0.1:8080` | HTTP server bind address |\n| `redis.addr` | string | `127.0.0.1:6379` | Redis server address |\n| `redis.password` | string | `\"\"` | Redis password |\n| `redis.db` | int | `0` | Redis database number |\n| `queue` | string | `default` | asynq queue name |\n| `concurrency` | int | `10` | Concurrent workers hitting upstream |\n| `jobsPath` | string | `/__jobs` | Base path for the jobs API endpoints |\n| `taskRetention` | duration | `1h` | How long completed/failed jobs stay in Redis before eviction |\n\nDuration values use Go syntax: `30s`, `5m`, `1h`, `1h30m`.\n\n### Upstreams (`upstreams[]`)\n\nRouted by longest path-prefix match; the matched prefix is stripped before forwarding.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `prefix` | string | **required** | URL path prefix for routing. Stripped before forwarding. |\n| `url` | string | **required** | Upstream server URL. May include a path (e.g. `http://api:3000/v2`). |\n| `timeout` | duration | `5m` | Per-upstream request timeout (overridable per-request via `X-Proxq-Timeout`) |\n| `maxRetries` | int | `0` | Retry attempts on transport failure. `0` = no retries. |\n| `retryDelay` | duration | `0` | Fixed delay between retries. `0` = exponential backoff (`n^4` seconds: 1s, 16s, 81s, ~4m, ~10m for attempts 1-5). |\n| `maxBodySize` | int (bytes) | `10485760` (10 MB) | Max request body buffered into the queue |\n| `directProxyThreshold` | int (bytes) | `10485760` (10 MB) | Body size above which requests bypass the queue entirely. `0` disables (always queue regardless of size, up to `maxBodySize`). |\n| `directProxyMode` | string | `proxy` | How bypassed requests reach upstream: `proxy` (reverse-proxied, client never sees upstream URL) or `redirect` (`307 Temporary Redirect` to the upstream URL) |\n| `cacheKeyExcludeHeaders` | list[string] | `[]` (defaults apply) | Headers excluded from the cache key. Empty list = built-in defaults (`X-Request-ID`, `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`). Setting this **replaces** the defaults entirely. |\n| `pathFilter.mode` | string | `blacklist` | `blacklist`: matching paths bypass the queue. `whitelist`: only matching paths get queued. |\n| `pathFilter.patterns` | list[string] | `[]` | Regex patterns matched against the request path. |\n\n### Cache (`cache`)\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `cache.mode` | string | `none` | `none`, `memory` (in-process LRU), or `redis` (shared, same Redis instance as the queue) |\n| `cache.ttl` | duration | `5m` | Freshness window |\n| `cache.maxEntries` | int | `10000` | Max entries for in-memory LRU mode |\n| `cache.redisKeyPrefix` | string | `proxq:` | Key prefix for Redis cache mode, avoids colliding with job data |\n\nCache rules: any HTTP method can be cached (same body → cache hit, different body → miss); only 2xx upstream responses are cached; cache key = `sha256(method + url + headers + body)` with volatile headers excluded per `cacheKeyExcludeHeaders`. Responses carry `X-Cache-Status: HIT` or `MISS`.\n\n### Direct proxy bypass — checked in this order\n\n| Condition | Why |\n|---|---|\n| WebSocket (`Connection: upgrade` + `Upgrade: websocket`) | Persistent bidirectional, can't queue |\n| Path filter match (per-upstream `pathFilter`) | Explicit opt-out |\n| Chunked transfer (`Transfer-Encoding: chunked`) | Size unknown |\n| Body over `directProxyThreshold` | Avoid buffering huge uploads into Redis |\n\n### Validation (fails startup if violated)\n\n- At least one upstream is required.\n- Every upstream needs both `prefix` and `url`.\n- Single upstream may use `prefix: \"/\"` (catch-all). Multiple upstreams may **not** include `prefix: \"/\"` — too ambiguous.\n- No nested prefixes (`/api` and `/api/v2` together is an error).\n- No upstream prefix may conflict with `jobsPath` (e.g. an upstream at `/__jobs` when `jobsPath` is `/__jobs`).\n- All `pathFilter.patterns` must be valid regexes.\n\n### Example config\n\n```yaml\nlistenAddress: \"0.0.0.0:8080\"\n\nredis:\n  addr: \"redis:6379\"\n  password: \"\"\n  db: 0\n\nqueue: \"default\"\nconcurrency: 10\njobsPath: \"/__jobs\"\ntaskRetention: \"1h\"\n\ncache:\n  mode: \"redis\"\n  ttl: \"10m\"\n  redisKeyPrefix: \"proxq:\"\n\nupstreams:\n  - prefix: \"/api\"\n    url: \"http://api-server:3000\"\n    timeout: \"5m\"\n    maxRetries: 3\n    retryDelay: \"10s\"\n    pathFilter:\n      mode: \"blacklist\"\n      patterns:\n        - \"^/api/auth\"\n        - \"^/api/health\"\n\n  - prefix: \"/uploads\"\n    url: \"http://file-server:9000/storage\"\n    timeout: \"10m\"\n    maxBodySize: 1073741824\n    directProxyThreshold: 0\n    directProxyMode: \"redirect\"\n```\n\n## Headers reference\n\nSet by proxq on responses it generates itself (never on responses proxied verbatim from upstream):\n\n| Header | Value | When |\n|---|---|---|\n| `X-Proxq-Source` | `proxq` | `202` accepted, `502` no upstream match, `500` internal errors, `307` redirects, `404` from job endpoints, reverse-proxy errors |\n| `X-Cache-Status` | `HIT` / `MISS` | On cached responses, when caching is enabled |\n\nAccepted from the client:\n\n| Header | Effect |\n|---|---|\n| `X-Proxq-Timeout` | Go duration string (e.g. `30s`), overrides the matched upstream's `timeout` for that one submitted request. Invalid value → `400`. |\n\nForwarded to upstream on every proxied/queued request: original headers as-is, plus `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`. Hop-by-hop headers (`Connection`, `Keep-Alive`, `Proxy-Authenticate`, `Proxy-Authorization`, `TE`, `Trailers`, `Transfer-Encoding`, `Upgrade`) are stripped per RFC 7230.\n\n## No built-in auth\n\nproxq has no authentication of its own — no API key, bearer token, or allowlist on any endpoint (job submission, status, content, cancel). Put it behind:\n- A reverse proxy doing auth (basic auth, OAuth2 proxy, mTLS) in front of `listenAddress`.\n- Network isolation — bind `listenAddress`/the published port to loopback or an internal-only network, never `0.0.0.0` on a publicly routable host without a fronting proxy.\n\nJob IDs are UUIDv4 (unguessable in practice) but there is **no ownership check** — anyone who can reach the instance and knows/guesses a job ID can poll or cancel it.\n\n## Management / development (operator-side, from the repo)\n\n```bash\nmake dep            # vendor dependencies\nmake lint           # golangci-lint\nmake test           # unit + integration tests (race detector on)\nmake test-coverage  # tests with 90% coverage threshold\nmake build           # docker build\n\n# e2e tests — spins up Redis + upstream + proxq via testcontainers\ncd tests && go test -v -timeout 10m ./...\n```\n\nFile v0.10.13:skill-card.md\n\n## Description:\n\nproxq helps an agent submit HTTP requests to a Redis-backed asynchronous proxy queue, poll job status, fetch replayed upstream responses, and provide Docker-based setup and configuration guidance.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[psyb0t](https://clawhub.ai/user/psyb0t)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and engineers use this skill to operate or interact with a running proxq instance when they need an async facade for slow or unreliable HTTP backends, webhook relay with retries, or queued upload and processing workflows.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: proxq can make outbound HTTP requests through configured upstreams, creating an SSRF exposure if untrusted callers or unsafe upstreams are allowed.\n\nMitigation: Run proxq on loopback or behind strong authentication, configure only trusted upstreams, and avoid exposing bare instances to untrusted networks.\n\nRisk: proxq has no built-in authentication or authorization for job submission, polling, content fetch, or cancellation.\n\nMitigation: Place proxq behind an authenticated reverse proxy, API gateway, mTLS layer, or internal-only network boundary before production use.\n\nRisk: Mutable container image tags can change after review.\n\nMitigation: Pin Docker images to reviewed versions or digests for production deployments.\n\nRisk: Requests may include headers, bodies, or secrets that are forwarded to configured upstream services.\n\nMitigation: Send secrets only through trusted deployments and upstreams, and review request contents before using proxq with sensitive data.\n\n## Reference(s):\n\n- [proxq setup](references/setup.md)\n- [proxq ClawHub page](https://clawhub.ai/psyb0t/skills/proxq)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance]\n\n**Output Format:** [Markdown with inline bash, YAML, JSON, and HTTP examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Guidance assumes access to a trusted proxq instance through PROXQ_URL and may include curl, docker, and docker compose commands.]\n\n## Skill Version(s):\n\n0.10.13 (source: server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v0.10.12: 4 files, 9472 bytes\n\nFiles: references/setup.md (8412b), skill-card.md (2438b), SKILL.md (8743b), _meta.json (126b)\n\nFile v0.10.12:SKILL.md\n\n---\nname: proxq\ndescription: Go, Redis-backed async HTTP proxy queue (built on asynq). POST any HTTP request (any method, any path, any body) to a configured upstream, get a job ID back instantly (202), a worker forwards it later, you poll GET /__jobs/{id} for status (queued/running/completed/failed) and GET /__jobs/{id}/content for the replayed upstream response (status/headers/body). DELETE /__jobs/{id} cancels. Path-prefix routing to multiple upstreams, per-upstream timeout/retries/pathFilter, optional response caching (memory or Redis LRU), automatic direct-proxy bypass for WebSocket/chunked/large-body requests. No built-in auth. Use when the user wants to turn a slow/unreliable backend into a fire-and-forget async API, decouple a client from upstream latency, relay webhooks with retries, or queue heavy uploads/processing jobs behind short-timeout reverse proxies.\nhomepage: https://github.com/psyb0t/docker-proxq\nuser-invocable: true\nmetadata:\n  { \"openclaw\": { \"emoji\": \"🦡\", \"primaryEnv\": \"PROXQ_URL\", \"requires\": { \"bins\": [\"curl\", \"docker\"] } } }\npermissions:\n  network: \"outbound HTTP to the configured PROXQ_URL (submit/poll/cancel job calls) — AND proxq itself makes arbitrary outbound HTTP requests to whatever upstream/URL you submit through it, on your behalf. That's an SSRF surface: only submit requests you intend proxq's configured upstreams to receive.\"\n  shell: \"curl + docker/docker-compose invocations shown in setup.md and this file (container lifecycle, request examples) — no other host access\"\n---\n\n# proxq\n\nThe honey badger of HTTP proxies. POST a request, get a job ID back instantly, come back later for the goods. \"I'll get back to you\" as a service — every HTTP request becomes an async job in a Redis-backed queue (via [asynq](https://github.com/hibiken/asynq)).\n\nFor installation, configuration, and container setup, see [references/setup.md](references/setup.md).\n\n## Security & safety\n\n- **This is an SSRF surface by design.** proxq's whole job is to make outbound HTTP requests to an upstream on your behalf — that's not a bug, it's the feature. Anyone who can submit a job through proxq gets proxq's network position to reach whatever `upstreams[].url` is configured (and, via `directProxyMode`/prefix stripping, whatever path/query you tack onto it). Never point an upstream at internal/admin services you wouldn't otherwise expose, and never let untrusted callers choose the upstream prefix or URL.\n- **No built-in authentication or authorization.** proxq ships with zero auth — no API key, no bearer token, no allowlist. Anyone who can reach `PROXQ_URL` can submit jobs, poll any job ID, and cancel any job ID (job IDs are UUIDv4 but there is no ownership check). Front it with a reverse proxy doing auth (basic auth, mTLS, an API gateway) or bind it to loopback/an internal network only — do not expose a bare proxq instance to the open internet.\n- **Trusted upstreams only.** Configure `upstreams[].url` to point only at backends you control or explicitly trust. proxq forwards the full original request (method, headers, body) plus `X-Forwarded-For`/`X-Real-IP`/`X-Forwarded-Proto` — treat the upstream config the same way you'd treat a reverse-proxy target list.\n- **Consumer-only.** This skill talks to an instance you (or your operator) already run and trust. It never provisions, hardens, or reconfigures the server — that's covered in setup.md as an explicit operator step.\n- **Cancelling a job** — `DELETE /__jobs/{id}` best-effort stops an in-flight job and deletes its record (no undo). Per the no-auth point above there's no ownership check, so only cancel a job you submitted or one the user explicitly named — don't guess IDs or bulk-cancel.\n\n## When To Use\n\n- Put an async facade in front of a slow backend so callers get an instant response instead of hanging on a long-running request.\n- Decouple a client from an upstream that occasionally times out — proxq queues the request, retries transport failures automatically, and the client polls at its own pace.\n- Sit behind a CDN/reverse-proxy with a short request timeout while your real backend takes minutes.\n- Relay webhooks without blocking the sender, with configurable retry/backoff.\n- Queue large uploads or long-running processing jobs (video, exports, reports) so the client doesn't hold a connection open.\n- Mix sync and async traffic on one gateway: fast paths (auth, health) bypass the queue via `pathFilter`, slow paths get queued.\n- Cache idempotent (or even non-idempotent-but-repeatable) responses so duplicate requests don't re-hit the upstream.\n\n## When NOT To Use\n\n- True real-time / streaming responses — the whole model is submit-then-poll; there's no push/webhook-back-to-caller notification built in.\n- WebSocket or chunked-transfer traffic that needs the queue semantics — those bypass the queue automatically and just get reverse-proxied straight through (see `directProxyMode` in setup.md), so don't expect a job ID for them.\n- As a public-facing endpoint without your own auth layer in front — proxq has none.\n- Pointing an upstream at anything you don't fully trust — proxq will happily forward arbitrary methods/bodies/headers to it.\n\n## Usage\n\nPoint at a running instance:\n\n```bash\nexport PROXQ_URL=http://localhost:8080\n```\n\nAll job-management endpoints live under `jobsPath` (default `/__jobs`). Every response proxq itself generates (not proxied from upstream) carries `X-Proxq-Source: proxq` — that's how you distinguish a proxq-origin response (job not ready, no upstream match, proxq error) from a real upstream response replayed verbatim.\n\n### Submit a job\n\nAny request that doesn't hit a job endpoint gets routed by longest-prefix match to a configured upstream and queued (unless it qualifies for [direct-proxy bypass](#direct-proxy-bypass) — see setup.md).\n\n```bash\ncurl -s -X POST \"$PROXQ_URL/api/heavy-computation\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer $UPSTREAM_TOKEN\" \\\n  -d '{\"data\": \"lots of it\"}'\n# 202 Accepted, X-Proxq-Source: proxq\n# {\"jobId\": \"550e8400-e29b-41d4-a716-446655440000\"}\n```\n\nIf no upstream prefix matches the request path: `502 Bad Gateway`, `X-Proxq-Source: proxq`.\n\nOptional per-request override header: `X-Proxq-Timeout: <go-duration>` (e.g. `X-Proxq-Timeout: 30s`) overrides the upstream's configured `timeout` for that one request. Invalid value → `400 Bad Request`.\n\n### Poll job status\n\n```bash\ncurl -s \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\"\n```\n\n```json\n{\"id\": \"550e8400-...\", \"status\": \"completed\", \"completedAt\": \"2025-01-01T00:00:00Z\"}\n```\n\nFailed job includes `error`:\n\n```json\n{\"id\": \"550e8400-...\", \"status\": \"failed\", \"error\": \"forward request: dial tcp: connection refused\"}\n```\n\n`status` is one of `queued` (pending/scheduled/aggregating), `running` (active, or waiting on a retry), `completed` (done — response stored, even if upstream returned 4xx/5xx), `failed` (transport broke and retries are exhausted). Unknown job ID → `404 Not Found`, `X-Proxq-Source: proxq`, body `{\"code\":\"NOT_FOUND\",\"message\":\"Not found\"}`.\n\n### Fetch job content (the payoff)\n\nReplays the upstream response exactly — status code, headers, body — as if you'd called upstream directly.\n\n```bash\ncurl -si \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000/content\"\n```\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/json\nX-Custom-Header: from-upstream\n\n{\"result\": \"done\"}\n```\n\nIf upstream returned a 404, you get 404 back too — but **without** `X-Proxq-Source` (it's a real upstream response). If the job isn't done yet, or doesn't exist: `404 Not Found` **with** `X-Proxq-Source: proxq`. That header is the whole disambiguation trick — present means \"proxq talking\", absent means \"upstream talking\".\n\n### Cancel a job\n\n```bash\ncurl -s -X DELETE \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\"\n# {\"status\": \"cancelled\"}\n```\n\nBest-effort: attempts to stop in-flight processing, then deletes the task record. Unknown job ID → `404 Not Found`, `X-Proxq-Source: proxq`. (See [Security & safety](#security--safety) — no ownership check, so only cancel jobs you own.)\n\n### Poll loop example\n\n```bash\nJOB_ID=$(curl -s -X POST \"$PROXQ_URL/api/report\" -d '{}' | jq -r .jobId)\n\nwhile :; do\n  STATUS=$(curl -s \"$PROXQ_URL/__jobs/$JOB_ID\" | jq -r .status)\n  case \"$STATUS\" in\n    completed) curl -s \"$PROXQ_URL/__jobs/$JOB_ID/content\" | jq; break ;;\n    failed)    echo \"job failed\" >&2; break ;;\n    *)         sleep 2 ;;\n  esac\ndone\n```\n\nFor upstream routing rules (prefix matching/stripping), direct-proxy bypass conditions, caching behavior, and the full config reference (env vars, docker run/compose), see [references/setup.md](references/setup.md).\n\nFile v0.10.12:_meta.json\n\n{\n  \"ownerId\": \"kn79dhvmpjng4rp2jjk8k0v5xx80ccbk\",\n  \"slug\": \"proxq\",\n  \"version\": \"0.10.12\",\n  \"publishedAt\": 1786201772844\n}\n\nFile v0.10.12:references/setup.md\n\n# proxq setup\n\n## Quick start (docker compose)\n\n```yaml\nservices:\n  proxq:\n    image: psyb0t/proxq\n    ports:\n      - \"127.0.0.1:8080:8080\"   # bind loopback-only; no built-in auth (see SKILL.md Security & safety)\n    environment:\n      PROXQ_CONFIG: /etc/proxq/config.yaml\n    configs:\n      - source: proxq_config\n        target: /etc/proxq/config.yaml\n    depends_on:\n      - redis\n\n  redis:\n    image: redis:7-alpine\n    restart: unless-stopped\n\nconfigs:\n  proxq_config:\n    content: |\n      listenAddress: \"0.0.0.0:8080\"\n      redis:\n        addr: \"redis:6379\"\n      upstreams:\n        - prefix: \"/\"\n          url: \"http://your-api:3000\"\n```\n\n```bash\ndocker compose up -d\ncurl http://127.0.0.1:8080/__jobs/nonexistent-id   # 404, X-Proxq-Source: proxq → confirms it's up\n```\n\n## Docker run (config file mounted from host)\n\n```bash\ndocker run -d \\\n  --name proxq \\\n  -p 127.0.0.1:8080:8080 \\\n  -e PROXQ_CONFIG=/etc/proxq/config.yaml \\\n  -v \"$(pwd)/config.yaml:/etc/proxq/config.yaml:ro\" \\\n  --link redis \\\n  psyb0t/proxq\n```\n\nRequires a reachable Redis instance (`redis:7-alpine` or any compatible server).\n\n## Config resolution\n\nConfig path is resolved in this order: `--config` CLI flag → `PROXQ_CONFIG` env var → `config.yaml` in the current directory. Everything else lives inside the YAML file itself — there is no per-field env var override for the rest of the settings, so mount/generate the YAML.\n\n| Env var / flag | Purpose |\n|---|---|\n| `--config <path>` | CLI flag, highest priority |\n| `PROXQ_CONFIG` | Path to the YAML config file, read if `--config` is unset |\n| `PUID` / `PGID` | Entrypoint runs the process as this uid:gid via `su-exec` (default `1000:1000`) — container-runtime detail, not app config |\n\n## Config file reference (YAML)\n\n### Global settings\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `listenAddress` | string | `127.0.0.1:8080` | HTTP server bind address |\n| `redis.addr` | string | `127.0.0.1:6379` | Redis server address |\n| `redis.password` | string | `\"\"` | Redis password |\n| `redis.db` | int | `0` | Redis database number |\n| `queue` | string | `default` | asynq queue name |\n| `concurrency` | int | `10` | Concurrent workers hitting upstream |\n| `jobsPath` | string | `/__jobs` | Base path for the jobs API endpoints |\n| `taskRetention` | duration | `1h` | How long completed/failed jobs stay in Redis before eviction |\n\nDuration values use Go syntax: `30s`, `5m`, `1h`, `1h30m`.\n\n### Upstreams (`upstreams[]`)\n\nRouted by longest path-prefix match; the matched prefix is stripped before forwarding.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `prefix` | string | **required** | URL path prefix for routing. Stripped before forwarding. |\n| `url` | string | **required** | Upstream server URL. May include a path (e.g. `http://api:3000/v2`). |\n| `timeout` | duration | `5m` | Per-upstream request timeout (overridable per-request via `X-Proxq-Timeout`) |\n| `maxRetries` | int | `0` | Retry attempts on transport failure. `0` = no retries. |\n| `retryDelay` | duration | `0` | Fixed delay between retries. `0` = exponential backoff (`n^4` seconds: 1s, 16s, 81s, ~4m, ~10m for attempts 1-5). |\n| `maxBodySize` | int (bytes) | `10485760` (10 MB) | Max request body buffered into the queue |\n| `directProxyThreshold` | int (bytes) | `10485760` (10 MB) | Body size above which requests bypass the queue entirely. `0` disables (always queue regardless of size, up to `maxBodySize`). |\n| `directProxyMode` | string | `proxy` | How bypassed requests reach upstream: `proxy` (reverse-proxied, client never sees upstream URL) or `redirect` (`307 Temporary Redirect` to the upstream URL) |\n| `cacheKeyExcludeHeaders` | list[string] | `[]` (defaults apply) | Headers excluded from the cache key. Empty list = built-in defaults (`X-Request-ID`, `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`). Setting this **replaces** the defaults entirely. |\n| `pathFilter.mode` | string | `blacklist` | `blacklist`: matching paths bypass the queue. `whitelist`: only matching paths get queued. |\n| `pathFilter.patterns` | list[string] | `[]` | Regex patterns matched against the request path. |\n\n### Cache (`cache`)\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `cache.mode` | string | `none` | `none`, `memory` (in-process LRU), or `redis` (shared, same Redis instance as the queue) |\n| `cache.ttl` | duration | `5m` | Freshness window |\n| `cache.maxEntries` | int | `10000` | Max entries for in-memory LRU mode |\n| `cache.redisKeyPrefix` | string | `proxq:` | Key prefix for Redis cache mode, avoids colliding with job data |\n\nCache rules: any HTTP method can be cached (same body → cache hit, different body → miss); only 2xx upstream responses are cached; cache key = `sha256(method + url + headers + body)` with volatile headers excluded per `cacheKeyExcludeHeaders`. Responses carry `X-Cache-Status: HIT` or `MISS`.\n\n### Direct proxy bypass — checked in this order\n\n| Condition | Why |\n|---|---|\n| WebSocket (`Connection: upgrade` + `Upgrade: websocket`) | Persistent bidirectional, can't queue |\n| Path filter match (per-upstream `pathFilter`) | Explicit opt-out |\n| Chunked transfer (`Transfer-Encoding: chunked`) | Size unknown |\n| Body over `directProxyThreshold` | Avoid buffering huge uploads into Redis |\n\n### Validation (fails startup if violated)\n\n- At least one upstream is required.\n- Every upstream needs both `prefix` and `url`.\n- Single upstream may use `prefix: \"/\"` (catch-all). Multiple upstreams may **not** include `prefix: \"/\"` — too ambiguous.\n- No nested prefixes (`/api` and `/api/v2` together is an error).\n- No upstream prefix may conflict with `jobsPath` (e.g. an upstream at `/__jobs` when `jobsPath` is `/__jobs`).\n- All `pathFilter.patterns` must be valid regexes.\n\n### Example config\n\n```yaml\nlistenAddress: \"0.0.0.0:8080\"\n\nredis:\n  addr: \"redis:6379\"\n  password: \"\"\n  db: 0\n\nqueue: \"default\"\nconcurrency: 10\njobsPath: \"/__jobs\"\ntaskRetention: \"1h\"\n\ncache:\n  mode: \"redis\"\n  ttl: \"10m\"\n  redisKeyPrefix: \"proxq:\"\n\nupstreams:\n  - prefix: \"/api\"\n    url: \"http://api-server:3000\"\n    timeout: \"5m\"\n    maxRetries: 3\n    retryDelay: \"10s\"\n    pathFilter:\n      mode: \"blacklist\"\n      patterns:\n        - \"^/api/auth\"\n        - \"^/api/health\"\n\n  - prefix: \"/uploads\"\n    url: \"http://file-server:9000/storage\"\n    timeout: \"10m\"\n    maxBodySize: 1073741824\n    directProxyThreshold: 0\n    directProxyMode: \"redirect\"\n```\n\n## Headers reference\n\nSet by proxq on responses it generates itself (never on responses proxied verbatim from upstream):\n\n| Header | Value | When |\n|---|---|---|\n| `X-Proxq-Source` | `proxq` | `202` accepted, `502` no upstream match, `500` internal errors, `307` redirects, `404` from job endpoints, reverse-proxy errors |\n| `X-Cache-Status` | `HIT` / `MISS` | On cached responses, when caching is enabled |\n\nAccepted from the client:\n\n| Header | Effect |\n|---|---|\n| `X-Proxq-Timeout` | Go duration string (e.g. `30s`), overrides the matched upstream's `timeout` for that one submitted request. Invalid value → `400`. |\n\nForwarded to upstream on every proxied/queued request: original headers as-is, plus `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`. Hop-by-hop headers (`Connection`, `Keep-Alive`, `Proxy-Authenticate`, `Proxy-Authorization`, `TE`, `Trailers`, `Transfer-Encoding`, `Upgrade`) are stripped per RFC 7230.\n\n## No built-in auth\n\nproxq has no authentication of its own — no API key, bearer token, or allowlist on any endpoint (job submission, status, content, cancel). Put it behind:\n- A reverse proxy doing auth (basic auth, OAuth2 proxy, mTLS) in front of `listenAddress`.\n- Network isolation — bind `listenAddress`/the published port to loopback or an internal-only network, never `0.0.0.0` on a publicly routable host without a fronting proxy.\n\nJob IDs are UUIDv4 (unguessable in practice) but there is **no ownership check** — anyone who can reach the instance and knows/guesses a job ID can poll or cancel it.\n\n## Management / development (operator-side, from the repo)\n\n```bash\nmake dep            # vendor dependencies\nmake lint           # golangci-lint\nmake test           # unit + integration tests (race detector on)\nmake test-coverage  # tests with 90% coverage threshold\nmake build           # docker build\n\n# e2e tests — spins up Redis + upstream + proxq via testcontainers\ncd tests && go test -v -timeout 10m ./...\n```\n\nFile v0.10.12:skill-card.md\n\n## Description:\n\nGo, Redis-backed async HTTP proxy queue that lets agents submit HTTP requests to a configured upstream, receive a job ID immediately, poll for status, fetch the replayed upstream response, and cancel jobs when needed.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[psyb0t](https://clawhub.ai/user/psyb0t)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and operators use this skill to interact with a trusted proxq instance that queues slow HTTP work, relays webhooks, manages heavy uploads, or shields clients from upstream latency by using submit, poll, fetch, and cancel workflows.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: proxq can relay requests from its own network position to configured upstreams, creating an SSRF surface if callers or configuration are untrusted.\n\nMitigation: Configure only trusted upstreams, avoid internal or administrative targets, and do not let untrusted callers choose upstream prefixes or URLs.\n\nRisk: proxq has no built-in authentication or authorization for submit, status, content, or cancel endpoints.\n\nMitigation: Keep PROXQ_URL private or authenticated, bind local deployments to loopback or an internal network, or place an authenticated reverse proxy or gateway in front of it.\n\nRisk: Shared instances can let reachable users view or cancel jobs when they know a job ID.\n\nMitigation: Avoid shared unauthenticated instances and only poll or cancel job IDs provided by the user or created during the current workflow.\n\n## Reference(s):\n\n- [proxq setup](artifact/references/setup.md)\n- [proxq ClawHub release](https://clawhub.ai/psyb0t/skills/proxq)\n- [asynq](https://github.com/hibiken/asynq)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance]\n\n**Output Format:** [Markdown with inline shell commands, JSON examples, HTTP examples, and YAML configuration snippets]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Outputs are intended for a running, trusted proxq deployment addressed by PROXQ_URL.]\n\n## Skill Version(s):\n\n0.10.12 (source: server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v0.10.11: 4 files, 9465 bytes\n\nFiles: references/setup.md (8412b), skill-card.md (2425b), SKILL.md (8743b), _meta.json (126b)\n\nFile v0.10.11:SKILL.md\n\n---\nname: proxq\ndescription: Go, Redis-backed async HTTP proxy queue (built on asynq). POST any HTTP request (any method, any path, any body) to a configured upstream, get a job ID back instantly (202), a worker forwards it later, you poll GET /__jobs/{id} for status (queued/running/completed/failed) and GET /__jobs/{id}/content for the replayed upstream response (status/headers/body). DELETE /__jobs/{id} cancels. Path-prefix routing to multiple upstreams, per-upstream timeout/retries/pathFilter, optional response caching (memory or Redis LRU), automatic direct-proxy bypass for WebSocket/chunked/large-body requests. No built-in auth. Use when the user wants to turn a slow/unreliable backend into a fire-and-forget async API, decouple a client from upstream latency, relay webhooks with retries, or queue heavy uploads/processing jobs behind short-timeout reverse proxies.\nhomepage: https://github.com/psyb0t/docker-proxq\nuser-invocable: true\nmetadata:\n  { \"openclaw\": { \"emoji\": \"🦡\", \"primaryEnv\": \"PROXQ_URL\", \"requires\": { \"bins\": [\"curl\", \"docker\"] } } }\npermissions:\n  network: \"outbound HTTP to the configured PROXQ_URL (submit/poll/cancel job calls) — AND proxq itself makes arbitrary outbound HTTP requests to whatever upstream/URL you submit through it, on your behalf. That's an SSRF surface: only submit requests you intend proxq's configured upstreams to receive.\"\n  shell: \"curl + docker/docker-compose invocations shown in setup.md and this file (container lifecycle, request examples) — no other host access\"\n---\n\n# proxq\n\nThe honey badger of HTTP proxies. POST a request, get a job ID back instantly, come back later for the goods. \"I'll get back to you\" as a service — every HTTP request becomes an async job in a Redis-backed queue (via [asynq](https://github.com/hibiken/asynq)).\n\nFor installation, configuration, and container setup, see [references/setup.md](references/setup.md).\n\n## Security & safety\n\n- **This is an SSRF surface by design.** proxq's whole job is to make outbound HTTP requests to an upstream on your behalf — that's not a bug, it's the feature. Anyone who can submit a job through proxq gets proxq's network position to reach whatever `upstreams[].url` is configured (and, via `directProxyMode`/prefix stripping, whatever path/query you tack onto it). Never point an upstream at internal/admin services you wouldn't otherwise expose, and never let untrusted callers choose the upstream prefix or URL.\n- **No built-in authentication or authorization.** proxq ships with zero auth — no API key, no bearer token, no allowlist. Anyone who can reach `PROXQ_URL` can submit jobs, poll any job ID, and cancel any job ID (job IDs are UUIDv4 but there is no ownership check). Front it with a reverse proxy doing auth (basic auth, mTLS, an API gateway) or bind it to loopback/an internal network only — do not expose a bare proxq instance to the open internet.\n- **Trusted upstreams only.** Configure `upstreams[].url` to point only at backends you control or explicitly trust. proxq forwards the full original request (method, headers, body) plus `X-Forwarded-For`/`X-Real-IP`/`X-Forwarded-Proto` — treat the upstream config the same way you'd treat a reverse-proxy target list.\n- **Consumer-only.** This skill talks to an instance you (or your operator) already run and trust. It never provisions, hardens, or reconfigures the server — that's covered in setup.md as an explicit operator step.\n- **Cancelling a job** — `DELETE /__jobs/{id}` best-effort stops an in-flight job and deletes its record (no undo). Per the no-auth point above there's no ownership check, so only cancel a job you submitted or one the user explicitly named — don't guess IDs or bulk-cancel.\n\n## When To Use\n\n- Put an async facade in front of a slow backend so callers get an instant response instead of hanging on a long-running request.\n- Decouple a client from an upstream that occasionally times out — proxq queues the request, retries transport failures automatically, and the client polls at its own pace.\n- Sit behind a CDN/reverse-proxy with a short request timeout while your real backend takes minutes.\n- Relay webhooks without blocking the sender, with configurable retry/backoff.\n- Queue large uploads or long-running processing jobs (video, exports, reports) so the client doesn't hold a connection open.\n- Mix sync and async traffic on one gateway: fast paths (auth, health) bypass the queue via `pathFilter`, slow paths get queued.\n- Cache idempotent (or even non-idempotent-but-repeatable) responses so duplicate requests don't re-hit the upstream.\n\n## When NOT To Use\n\n- True real-time / streaming responses — the whole model is submit-then-poll; there's no push/webhook-back-to-caller notification built in.\n- WebSocket or chunked-transfer traffic that needs the queue semantics — those bypass the queue automatically and just get reverse-proxied straight through (see `directProxyMode` in setup.md), so don't expect a job ID for them.\n- As a public-facing endpoint without your own auth layer in front — proxq has none.\n- Pointing an upstream at anything you don't fully trust — proxq will happily forward arbitrary methods/bodies/headers to it.\n\n## Usage\n\nPoint at a running instance:\n\n```bash\nexport PROXQ_URL=http://localhost:8080\n```\n\nAll job-management endpoints live under `jobsPath` (default `/__jobs`). Every response proxq itself generates (not proxied from upstream) carries `X-Proxq-Source: proxq` — that's how you distinguish a proxq-origin response (job not ready, no upstream match, proxq error) from a real upstream response replayed verbatim.\n\n### Submit a job\n\nAny request that doesn't hit a job endpoint gets routed by longest-prefix match to a configured upstream and queued (unless it qualifies for [direct-proxy bypass](#direct-proxy-bypass) — see setup.md).\n\n```bash\ncurl -s -X POST \"$PROXQ_URL/api/heavy-computation\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer $UPSTREAM_TOKEN\" \\\n  -d '{\"data\": \"lots of it\"}'\n# 202 Accepted, X-Proxq-Source: proxq\n# {\"jobId\": \"550e8400-e29b-41d4-a716-446655440000\"}\n```\n\nIf no upstream prefix matches the request path: `502 Bad Gateway`, `X-Proxq-Source: proxq`.\n\nOptional per-request override header: `X-Proxq-Timeout: <go-duration>` (e.g. `X-Proxq-Timeout: 30s`) overrides the upstream's configured `timeout` for that one request. Invalid value → `400 Bad Request`.\n\n### Poll job status\n\n```bash\ncurl -s \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\"\n```\n\n```json\n{\"id\": \"550e8400-...\", \"status\": \"completed\", \"completedAt\": \"2025-01-01T00:00:00Z\"}\n```\n\nFailed job includes `error`:\n\n```json\n{\"id\": \"550e8400-...\", \"status\": \"failed\", \"error\": \"forward request: dial tcp: connection refused\"}\n```\n\n`status` is one of `queued` (pending/scheduled/aggregating), `running` (active, or waiting on a retry), `completed` (done — response stored, even if upstream returned 4xx/5xx), `failed` (transport broke and retries are exhausted). Unknown job ID → `404 Not Found`, `X-Proxq-Source: proxq`, body `{\"code\":\"NOT_FOUND\",\"message\":\"Not found\"}`.\n\n### Fetch job content (the payoff)\n\nReplays the upstream response exactly — status code, headers, body — as if you'd called upstream directly.\n\n```bash\ncurl -si \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000/content\"\n```\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/json\nX-Custom-Header: from-upstream\n\n{\"result\": \"done\"}\n```\n\nIf upstream returned a 404, you get 404 back too — but **without** `X-Proxq-Source` (it's a real upstream response). If the job isn't done yet, or doesn't exist: `404 Not Found` **with** `X-Proxq-Source: proxq`. That header is the whole disambiguation trick — present means \"proxq talking\", absent means \"upstream talking\".\n\n### Cancel a job\n\n```bash\ncurl -s -X DELETE \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\"\n# {\"status\": \"cancelled\"}\n```\n\nBest-effort: attempts to stop in-flight processing, then deletes the task record. Unknown job ID → `404 Not Found`, `X-Proxq-Source: proxq`. (See [Security & safety](#security--safety) — no ownership check, so only cancel jobs you own.)\n\n### Poll loop example\n\n```bash\nJOB_ID=$(curl -s -X POST \"$PROXQ_URL/api/report\" -d '{}' | jq -r .jobId)\n\nwhile :; do\n  STATUS=$(curl -s \"$PROXQ_URL/__jobs/$JOB_ID\" | jq -r .status)\n  case \"$STATUS\" in\n    completed) curl -s \"$PROXQ_URL/__jobs/$JOB_ID/content\" | jq; break ;;\n    failed)    echo \"job failed\" >&2; break ;;\n    *)         sleep 2 ;;\n  esac\ndone\n```\n\nFor upstream routing rules (prefix matching/stripping), direct-proxy bypass conditions, caching behavior, and the full config reference (env vars, docker run/compose), see [references/setup.md](references/setup.md).\n\nFile v0.10.11:_meta.json\n\n{\n  \"ownerId\": \"kn79dhvmpjng4rp2jjk8k0v5xx80ccbk\",\n  \"slug\": \"proxq\",\n  \"version\": \"0.10.11\",\n  \"publishedAt\": 1786183328526\n}\n\nFile v0.10.11:references/setup.md\n\n# proxq setup\n\n## Quick start (docker compose)\n\n```yaml\nservices:\n  proxq:\n    image: psyb0t/proxq\n    ports:\n      - \"127.0.0.1:8080:8080\"   # bind loopback-only; no built-in auth (see SKILL.md Security & safety)\n    environment:\n      PROXQ_CONFIG: /etc/proxq/config.yaml\n    configs:\n      - source: proxq_config\n        target: /etc/proxq/config.yaml\n    depends_on:\n      - redis\n\n  redis:\n    image: redis:7-alpine\n    restart: unless-stopped\n\nconfigs:\n  proxq_config:\n    content: |\n      listenAddress: \"0.0.0.0:8080\"\n      redis:\n        addr: \"redis:6379\"\n      upstreams:\n        - prefix: \"/\"\n          url: \"http://your-api:3000\"\n```\n\n```bash\ndocker compose up -d\ncurl http://127.0.0.1:8080/__jobs/nonexistent-id   # 404, X-Proxq-Source: proxq → confirms it's up\n```\n\n## Docker run (config file mounted from host)\n\n```bash\ndocker run -d \\\n  --name proxq \\\n  -p 127.0.0.1:8080:8080 \\\n  -e PROXQ_CONFIG=/etc/proxq/config.yaml \\\n  -v \"$(pwd)/config.yaml:/etc/proxq/config.yaml:ro\" \\\n  --link redis \\\n  psyb0t/proxq\n```\n\nRequires a reachable Redis instance (`redis:7-alpine` or any compatible server).\n\n## Config resolution\n\nConfig path is resolved in this order: `--config` CLI flag → `PROXQ_CONFIG` env var → `config.yaml` in the current directory. Everything else lives inside the YAML file itself — there is no per-field env var override for the rest of the settings, so mount/generate the YAML.\n\n| Env var / flag | Purpose |\n|---|---|\n| `--config <path>` | CLI flag, highest priority |\n| `PROXQ_CONFIG` | Path to the YAML config file, read if `--config` is unset |\n| `PUID` / `PGID` | Entrypoint runs the process as this uid:gid via `su-exec` (default `1000:1000`) — container-runtime detail, not app config |\n\n## Config file reference (YAML)\n\n### Global settings\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `listenAddress` | string | `127.0.0.1:8080` | HTTP server bind address |\n| `redis.addr` | string | `127.0.0.1:6379` | Redis server address |\n| `redis.password` | string | `\"\"` | Redis password |\n| `redis.db` | int | `0` | Redis database number |\n| `queue` | string | `default` | asynq queue name |\n| `concurrency` | int | `10` | Concurrent workers hitting upstream |\n| `jobsPath` | string | `/__jobs` | Base path for the jobs API endpoints |\n| `taskRetention` | duration | `1h` | How long completed/failed jobs stay in Redis before eviction |\n\nDuration values use Go syntax: `30s`, `5m`, `1h`, `1h30m`.\n\n### Upstreams (`upstreams[]`)\n\nRouted by longest path-prefix match; the matched prefix is stripped before forwarding.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `prefix` | string | **required** | URL path prefix for routing. Stripped before forwarding. |\n| `url` | string | **required** | Upstream server URL. May include a path (e.g. `http://api:3000/v2`). |\n| `timeout` | duration | `5m` | Per-upstream request timeout (overridable per-request via `X-Proxq-Timeout`) |\n| `maxRetries` | int | `0` | Retry attempts on transport failure. `0` = no retries. |\n| `retryDelay` | duration | `0` | Fixed delay between retries. `0` = exponential backoff (`n^4` seconds: 1s, 16s, 81s, ~4m, ~10m for attempts 1-5). |\n| `maxBodySize` | int (bytes) | `10485760` (10 MB) | Max request body buffered into the queue |\n| `directProxyThreshold` | int (bytes) | `10485760` (10 MB) | Body size above which requests bypass the queue entirely. `0` disables (always queue regardless of size, up to `maxBodySize`). |\n| `directProxyMode` | string | `proxy` | How bypassed requests reach upstream: `proxy` (reverse-proxied, client never sees upstream URL) or `redirect` (`307 Temporary Redirect` to the upstream URL) |\n| `cacheKeyExcludeHeaders` | list[string] | `[]` (defaults apply) | Headers excluded from the cache key. Empty list = built-in defaults (`X-Request-ID`, `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`). Setting this **replaces** the defaults entirely. |\n| `pathFilter.mode` | string | `blacklist` | `blacklist`: matching paths bypass the queue. `whitelist`: only matching paths get queued. |\n| `pathFilter.patterns` | list[string] | `[]` | Regex patterns matched against the request path. |\n\n### Cache (`cache`)\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `cache.mode` | string | `none` | `none`, `memory` (in-process LRU), or `redis` (shared, same Redis instance as the queue) |\n| `cache.ttl` | duration | `5m` | Freshness window |\n| `cache.maxEntries` | int | `10000` | Max entries for in-memory LRU mode |\n| `cache.redisKeyPrefix` | string | `proxq:` | Key prefix for Redis cache mode, avoids colliding with job data |\n\nCache rules: any HTTP method can be cached (same body → cache hit, different body → miss); only 2xx upstream responses are cached; cache key = `sha256(method + url + headers + body)` with volatile headers excluded per `cacheKeyExcludeHeaders`. Responses carry `X-Cache-Status: HIT` or `MISS`.\n\n### Direct proxy bypass — checked in this order\n\n| Condition | Why |\n|---|---|\n| WebSocket (`Connection: upgrade` + `Upgrade: websocket`) | Persistent bidirectional, can't queue |\n| Path filter match (per-upstream `pathFilter`) | Explicit opt-out |\n| Chunked transfer (`Transfer-Encoding: chunked`) | Size unknown |\n| Body over `directProxyThreshold` | Avoid buffering huge uploads into Redis |\n\n### Validation (fails startup if violated)\n\n- At least one upstream is required.\n- Every upstream needs both `prefix` and `url`.\n- Single upstream may use `prefix: \"/\"` (catch-all). Multiple upstreams may **not** include `prefix: \"/\"` — too ambiguous.\n- No nested prefixes (`/api` and `/api/v2` together is an error).\n- No upstream prefix may conflict with `jobsPath` (e.g. an upstream at `/__jobs` when `jobsPath` is `/__jobs`).\n- All `pathFilter.patterns` must be valid regexes.\n\n### Example config\n\n```yaml\nlistenAddress: \"0.0.0.0:8080\"\n\nredis:\n  addr: \"redis:6379\"\n  password: \"\"\n  db: 0\n\nqueue: \"default\"\nconcurrency: 10\njobsPath: \"/__jobs\"\ntaskRetention: \"1h\"\n\ncache:\n  mode: \"redis\"\n  ttl: \"10m\"\n  redisKeyPrefix: \"proxq:\"\n\nupstreams:\n  - prefix: \"/api\"\n    url: \"http://api-server:3000\"\n    timeout: \"5m\"\n    maxRetries: 3\n    retryDelay: \"10s\"\n    pathFilter:\n      mode: \"blacklist\"\n      patterns:\n        - \"^/api/auth\"\n        - \"^/api/health\"\n\n  - prefix: \"/uploads\"\n    url: \"http://file-server:9000/storage\"\n    timeout: \"10m\"\n    maxBodySize: 1073741824\n    directProxyThreshold: 0\n    directProxyMode: \"redirect\"\n```\n\n## Headers reference\n\nSet by proxq on responses it generates itself (never on responses proxied verbatim from upstream):\n\n| Header | Value | When |\n|---|---|---|\n| `X-Proxq-Source` | `proxq` | `202` accepted, `502` no upstream match, `500` internal errors, `307` redirects, `404` from job endpoints, reverse-proxy errors |\n| `X-Cache-Status` | `HIT` / `MISS` | On cached responses, when caching is enabled |\n\nAccepted from the client:\n\n| Header | Effect |\n|---|---|\n| `X-Proxq-Timeout` | Go duration string (e.g. `30s`), overrides the matched upstream's `timeout` for that one submitted request. Invalid value → `400`. |\n\nForwarded to upstream on every proxied/queued request: original headers as-is, plus `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`. Hop-by-hop headers (`Connection`, `Keep-Alive`, `Proxy-Authenticate`, `Proxy-Authorization`, `TE`, `Trailers`, `Transfer-Encoding`, `Upgrade`) are stripped per RFC 7230.\n\n## No built-in auth\n\nproxq has no authentication of its own — no API key, bearer token, or allowlist on any endpoint (job submission, status, content, cancel). Put it behind:\n- A reverse proxy doing auth (basic auth, OAuth2 proxy, mTLS) in front of `listenAddress`.\n- Network isolation — bind `listenAddress`/the published port to loopback or an internal-only network, never `0.0.0.0` on a publicly routable host without a fronting proxy.\n\nJob IDs are UUIDv4 (unguessable in practice) but there is **no ownership check** — anyone who can reach the instance and knows/guesses a job ID can poll or cancel it.\n\n## Management / development (operator-side, from the repo)\n\n```bash\nmake dep            # vendor dependencies\nmake lint           # golangci-lint\nmake test           # unit + integration tests (race detector on)\nmake test-coverage  # tests with 90% coverage threshold\nmake build           # docker build\n\n# e2e tests — spins up Redis + upstream + proxq via testcontainers\ncd tests && go test -v -timeout 10m ./...\n```\n\nFile v0.10.11:skill-card.md\n\n## Description:\n\nGo, Redis-backed async HTTP proxy queue that lets an agent submit HTTP requests to configured upstreams, receive a job ID immediately, poll job status, fetch the replayed upstream response, and cancel jobs.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[psyb0t](https://clawhub.ai/user/psyb0t)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and engineers use this skill to operate a running proxq instance as an async facade for slow, unreliable, or timeout-prone HTTP backends. It is useful for queued API calls, webhook relays, heavy uploads, long-running processing jobs, response polling, and cancellation workflows.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: proxq is an HTTP relay, so submitted jobs can cause outbound requests from proxq's network position.\n\nMitigation: Keep PROXQ_URL private or behind authentication and configure upstreams only for services the operator controls or explicitly trusts.\n\nRisk: The proxq service has no built-in authentication or job ownership checks.\n\nMitigation: Use a fronting reverse proxy, mTLS, API gateway, or network isolation, and only cancel jobs submitted by the user or explicitly named by the user.\n\nRisk: Forwarded headers and request bodies may include secrets intended only for an approved upstream.\n\nMitigation: Avoid forwarding secrets unless the configured destination is approved and treat upstream configuration as a trusted routing boundary.\n\n## Reference(s):\n\n- [proxq setup](references/setup.md)\n- [ClawHub skill page](https://clawhub.ai/psyb0t/skills/proxq)\n- [proxq homepage](https://github.com/psyb0t/docker-proxq)\n- [asynq](https://github.com/hibiken/asynq)\n\n## Skill Output:\n\n**Output Type(s):** [guidance, shell commands, configuration, code, markdown]\n\n**Output Format:** [Markdown with inline shell commands, JSON examples, HTTP examples, and YAML configuration snippets]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Guidance is scoped to a user-provided PROXQ_URL and a running proxq deployment.]\n\n## Skill Version(s):\n\n0.10.11 (source: server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v0.10.10: 4 files, 9471 bytes\n\nFiles: references/setup.md (8412b), skill-card.md (2537b), SKILL.md (8742b), _meta.json (126b)\n\nFile v0.10.10:SKILL.md\n\n---\nname: proxq\ndescription: Go, Redis-backed async HTTP proxy queue (built on asynq). POST any HTTP request (any method, any path, any body) to a configured upstream, get a job ID back instantly (202), a worker forwards it later, you poll GET /__jobs/{id} for status (queued/running/completed/failed) and GET /__jobs/{id}/content for the replayed upstream response (status/headers/body). DELETE /__jobs/{id} cancels. Path-prefix routing to multiple upstreams, per-upstream timeout/retries/pathFilter, optional response caching (memory or Redis LRU), automatic direct-proxy bypass for WebSocket/chunked/large-body requests. No built-in auth. Use when the user wants to turn a slow/unreliable backend into a fire-and-forget async API, decouple a client from upstream latency, relay webhooks with retries, or queue heavy uploads/processing jobs behind short-timeout reverse proxies.\nhomepage: https://github.com/psyb0t/docker-proxq\nuser-invocable: true\nmetadata:\n  { \"openclaw\": { \"emoji\": \"🦡\", \"primaryEnv\": \"PROXQ_URL\", \"requires\": { \"bins\": [\"curl\", \"docker\"] } } }\npermissions:\n  network: \"outbound HTTP to the configured PROXQ_URL (submit/poll/cancel job calls) — AND proxq itself makes arbitrary outbound HTTP requests to whatever upstream/URL you submit through it, on your behalf. That's an SSRF surface: only submit requests you intend proxq's configured upstreams to receive.\"\n  shell: \"curl + docker/docker-compose invocations shown in setup.md and this file (container lifecycle, request examples) — no other host access\"\n---\n\n# proxq\n\nThe honey badger of HTTP proxies. POST a request, get a job ID back instantly, come back later for the goods. \"I'll get back to you\" as a service — every HTTP request becomes an async job in a Redis-backed queue (via [asynq](https://github.com/hibiken/asynq)).\n\nFor installation, configuration, and container setup, see [references/setup.md](references/setup.md).\n\n## Security & safety\n\n- **This is an SSRF surface by design.** proxq's whole job is to make outbound HTTP requests to an upstream on your behalf — that's not a bug, it's the feature. Anyone who can submit a job through proxq gets proxq's network position to reach whatever `upstreams[].url` is configured (and, via `directProxyMode`/prefix stripping, whatever path/query you tack onto it). Never point an upstream at internal/admin services you wouldn't otherwise expose, and never let untrusted callers choose the upstream prefix or URL.\n- **No built-in authentication or authorization.** proxq ships with zero auth — no API key, no bearer token, no allowlist. Anyone who can reach `PROXQ_URL` can submit jobs, poll any job ID, and cancel any job ID (job IDs are UUIDv4 but there is no ownership check). Front it with a reverse proxy doing auth (basic auth, mTLS, an API gateway) or bind it to loopback/an internal network only — do not expose a bare proxq instance to the open internet.\n- **Trusted upstreams only.** Configure `upstreams[].url` to point only at backends you control or explicitly trust. proxq forwards the full original request (method, headers, body) plus `X-Forwarded-For`/`X-Real-IP`/`X-Forwarded-Proto` — treat the upstream config the same way you'd treat a reverse-proxy target list.\n- **Consumer-only.** This skill talks to an instance you (or your operator) already run and trust. It never provisions, hardens, or reconfigures the server — that's covered in setup.md as an explicit operator step.\n- **Cancelling a job** — `DELETE /__jobs/{id}` best-effort stops an in-flight job and deletes its record (no undo). Per the no-auth point above there's no ownership check, so only cancel a job you submitted or one the user explicitly named — don't guess IDs or bulk-cancel.\n\n## When To Use\n\n- Put an async facade in front of a slow backend so callers get an instant response instead of hanging on a long-running request.\n- Decouple a client from an upstream that occasionally times out — proxq queues the request, retries transport failures automatically, and the client polls at its own pace.\n- Sit behind a CDN/reverse-proxy with a short request timeout while your real backend takes minutes.\n- Relay webhooks without blocking the sender, with configurable retry/backoff.\n- Queue large uploads or long-running processing jobs (video, exports, reports) so the client doesn't hold a connection open.\n- Mix sync and async traffic on one gateway: fast paths (auth, health) bypass the queue via `pathFilter`, slow paths get queued.\n- Cache idempotent (or even non-idempotent-but-repeatable) responses so duplicate requests don't re-hit the upstream.\n\n## When NOT To Use\n\n- True real-time / streaming responses — the whole model is submit-then-poll; there's no push/webhook-back-to-caller notification built in.\n- WebSocket or chunked-transfer traffic that needs the queue semantics — those bypass the queue automatically and just get reverse-proxied straight through (see `directProxyMode` in setup.md), so don't expect a job ID for them.\n- As a public-facing endpoint without your own auth layer in front — proxq has none.\n- Pointing an upstream at anything you don't fully trust — proxq will happily forward arbitrary methods/bodies/headers to it.\n\n## Usage\n\nPoint at a running instance:\n\n```bash\nexport PROXQ_URL=http://localhost:8080\n```\n\nAll job-management endpoints live under `jobsPath` (default `/__jobs`). Every response proxq itself generates (not proxied from upstream) carries `X-Proxq-Source: proxq` — that's how you distinguish a proxq-origin response (job not ready, no upstream match, proxq error) from a real upstream response replayed verbatim.\n\n### Submit a job\n\nAny request that doesn't hit a job endpoint gets routed by longest-prefix match to a configured upstream and queued (unless it qualifies for [direct-proxy bypass](#direct-proxy-bypass) — see setup.md).\n\n```bash\ncurl -s -X POST \"$PROXQ_URL/api/heavy-computation\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer upstream-token\" \\\n  -d '{\"data\": \"lots of it\"}'\n# 202 Accepted, X-Proxq-Source: proxq\n# {\"jobId\": \"550e8400-e29b-41d4-a716-446655440000\"}\n```\n\nIf no upstream prefix matches the request path: `502 Bad Gateway`, `X-Proxq-Source: proxq`.\n\nOptional per-request override header: `X-Proxq-Timeout: <go-duration>` (e.g. `X-Proxq-Timeout: 30s`) overrides the upstream's configured `timeout` for that one request. Invalid value → `400 Bad Request`.\n\n### Poll job status\n\n```bash\ncurl -s \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\"\n```\n\n```json\n{\"id\": \"550e8400-...\", \"status\": \"completed\", \"completedAt\": \"2025-01-01T00:00:00Z\"}\n```\n\nFailed job includes `error`:\n\n```json\n{\"id\": \"550e8400-...\", \"status\": \"failed\", \"error\": \"forward request: dial tcp: connection refused\"}\n```\n\n`status` is one of `queued` (pending/scheduled/aggregating), `running` (active, or waiting on a retry), `completed` (done — response stored, even if upstream returned 4xx/5xx), `failed` (transport broke and retries are exhausted). Unknown job ID → `404 Not Found`, `X-Proxq-Source: proxq`, body `{\"code\":\"NOT_FOUND\",\"message\":\"Not found\"}`.\n\n### Fetch job content (the payoff)\n\nReplays the upstream response exactly — status code, headers, body — as if you'd called upstream directly.\n\n```bash\ncurl -si \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000/content\"\n```\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/json\nX-Custom-Header: from-upstream\n\n{\"result\": \"done\"}\n```\n\nIf upstream returned a 404, you get 404 back too — but **without** `X-Proxq-Source` (it's a real upstream response). If the job isn't done yet, or doesn't exist: `404 Not Found` **with** `X-Proxq-Source: proxq`. That header is the whole disambiguation trick — present means \"proxq talking\", absent means \"upstream talking\".\n\n### Cancel a job\n\n```bash\ncurl -s -X DELETE \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\"\n# {\"status\": \"cancelled\"}\n```\n\nBest-effort: attempts to stop in-flight processing, then deletes the task record. Unknown job ID → `404 Not Found`, `X-Proxq-Source: proxq`. (See [Security & safety](#security--safety) — no ownership check, so only cancel jobs you own.)\n\n### Poll loop example\n\n```bash\nJOB_ID=$(curl -s -X POST \"$PROXQ_URL/api/report\" -d '{}' | jq -r .jobId)\n\nwhile :; do\n  STATUS=$(curl -s \"$PROXQ_URL/__jobs/$JOB_ID\" | jq -r .status)\n  case \"$STATUS\" in\n    completed) curl -s \"$PROXQ_URL/__jobs/$JOB_ID/content\" | jq; break ;;\n    failed)    echo \"job failed\" >&2; break ;;\n    *)         sleep 2 ;;\n  esac\ndone\n```\n\nFor upstream routing rules (prefix matching/stripping), direct-proxy bypass conditions, caching behavior, and the full config reference (env vars, docker run/compose), see [references/setup.md](references/setup.md).\n\nFile v0.10.10:_meta.json\n\n{\n  \"ownerId\": \"kn79dhvmpjng4rp2jjk8k0v5xx80ccbk\",\n  \"slug\": \"proxq\",\n  \"version\": \"0.10.10\",\n  \"publishedAt\": 1785616388917\n}\n\nFile v0.10.10:references/setup.md\n\n# proxq setup\n\n## Quick start (docker compose)\n\n```yaml\nservices:\n  proxq:\n    image: psyb0t/proxq\n    ports:\n      - \"127.0.0.1:8080:8080\"   # bind loopback-only; no built-in auth (see SKILL.md Security & safety)\n    environment:\n      PROXQ_CONFIG: /etc/proxq/config.yaml\n    configs:\n      - source: proxq_config\n        target: /etc/proxq/config.yaml\n    depends_on:\n      - redis\n\n  redis:\n    image: redis:7-alpine\n    restart: unless-stopped\n\nconfigs:\n  proxq_config:\n    content: |\n      listenAddress: \"0.0.0.0:8080\"\n      redis:\n        addr: \"redis:6379\"\n      upstreams:\n        - prefix: \"/\"\n          url: \"http://your-api:3000\"\n```\n\n```bash\ndocker compose up -d\ncurl http://127.0.0.1:8080/__jobs/nonexistent-id   # 404, X-Proxq-Source: proxq → confirms it's up\n```\n\n## Docker run (config file mounted from host)\n\n```bash\ndocker run -d \\\n  --name proxq \\\n  -p 127.0.0.1:8080:8080 \\\n  -e PROXQ_CONFIG=/etc/proxq/config.yaml \\\n  -v \"$(pwd)/config.yaml:/etc/proxq/config.yaml:ro\" \\\n  --link redis \\\n  psyb0t/proxq\n```\n\nRequires a reachable Redis instance (`redis:7-alpine` or any compatible server).\n\n## Config resolution\n\nConfig path is resolved in this order: `--config` CLI flag → `PROXQ_CONFIG` env var → `config.yaml` in the current directory. Everything else lives inside the YAML file itself — there is no per-field env var override for the rest of the settings, so mount/generate the YAML.\n\n| Env var / flag | Purpose |\n|---|---|\n| `--config <path>` | CLI flag, highest priority |\n| `PROXQ_CONFIG` | Path to the YAML config file, read if `--config` is unset |\n| `PUID` / `PGID` | Entrypoint runs the process as this uid:gid via `su-exec` (default `1000:1000`) — container-runtime detail, not app config |\n\n## Config file reference (YAML)\n\n### Global settings\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `listenAddress` | string | `127.0.0.1:8080` | HTTP server bind address |\n| `redis.addr` | string | `127.0.0.1:6379` | Redis server address |\n| `redis.password` | string | `\"\"` | Redis password |\n| `redis.db` | int | `0` | Redis database number |\n| `queue` | string | `default` | asynq queue name |\n| `concurrency` | int | `10` | Concurrent workers hitting upstream |\n| `jobsPath` | string | `/__jobs` | Base path for the jobs API endpoints |\n| `taskRetention` | duration | `1h` | How long completed/failed jobs stay in Redis before eviction |\n\nDuration values use Go syntax: `30s`, `5m`, `1h`, `1h30m`.\n\n### Upstreams (`upstreams[]`)\n\nRouted by longest path-prefix match; the matched prefix is stripped before forwarding.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `prefix` | string | **required** | URL path prefix for routing. Stripped before forwarding. |\n| `url` | string | **required** | Upstream server URL. May include a path (e.g. `http://api:3000/v2`). |\n| `timeout` | duration | `5m` | Per-upstream request timeout (overridable per-request via `X-Proxq-Timeout`) |\n| `maxRetries` | int | `0` | Retry attempts on transport failure. `0` = no retries. |\n| `retryDelay` | duration | `0` | Fixed delay between retries. `0` = exponential backoff (`n^4` seconds: 1s, 16s, 81s, ~4m, ~10m for attempts 1-5). |\n| `maxBodySize` | int (bytes) | `10485760` (10 MB) | Max request body buffered into the queue |\n| `directProxyThreshold` | int (bytes) | `10485760` (10 MB) | Body size above which requests bypass the queue entirely. `0` disables (always queue regardless of size, up to `maxBodySize`). |\n| `directProxyMode` | string | `proxy` | How bypassed requests reach upstream: `proxy` (reverse-proxied, client never sees upstream URL) or `redirect` (`307 Temporary Redirect` to the upstream URL) |\n| `cacheKeyExcludeHeaders` | list[string] | `[]` (defaults apply) | Headers excluded from the cache key. Empty list = built-in defaults (`X-Request-ID`, `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`). Setting this **replaces** the defaults entirely. |\n| `pathFilter.mode` | string | `blacklist` | `blacklist`: matching paths bypass the queue. `whitelist`: only matching paths get queued. |\n| `pathFilter.patterns` | list[string] | `[]` | Regex patterns matched against the request path. |\n\n### Cache (`cache`)\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `cache.mode` | string | `none` | `none`, `memory` (in-process LRU), or `redis` (shared, same Redis instance as the queue) |\n| `cache.ttl` | duration | `5m` | Freshness window |\n| `cache.maxEntries` | int | `10000` | Max entries for in-memory LRU mode |\n| `cache.redisKeyPrefix` | string | `proxq:` | Key prefix for Redis cache mode, avoids colliding with job data |\n\nCache rules: any HTTP method can be cached (same body → cache hit, different body → miss); only 2xx upstream responses are cached; cache key = `sha256(method + url + headers + body)` with volatile headers excluded per `cacheKeyExcludeHeaders`. Responses carry `X-Cache-Status: HIT` or `MISS`.\n\n### Direct proxy bypass — checked in this order\n\n| Condition | Why |\n|---|---|\n| WebSocket (`Connection: upgrade` + `Upgrade: websocket`) | Persistent bidirectional, can't queue |\n| Path filter match (per-upstream `pathFilter`) | Explicit opt-out |\n| Chunked transfer (`Transfer-Encoding: chunked`) | Size unknown |\n| Body over `directProxyThreshold` | Avoid buffering huge uploads into Redis |\n\n### Validation (fails startup if violated)\n\n- At least one upstream is required.\n- Every upstream needs both `prefix` and `url`.\n- Single upstream may use `prefix: \"/\"` (catch-all). Multiple upstreams may **not** include `prefix: \"/\"` — too ambiguous.\n- No nested prefixes (`/api` and `/api/v2` together is an error).\n- No upstream prefix may conflict with `jobsPath` (e.g. an upstream at `/__jobs` when `jobsPath` is `/__jobs`).\n- All `pathFilter.patterns` must be valid regexes.\n\n### Example config\n\n```yaml\nlistenAddress: \"0.0.0.0:8080\"\n\nredis:\n  addr: \"redis:6379\"\n  password: \"\"\n  db: 0\n\nqueue: \"default\"\nconcurrency: 10\njobsPath: \"/__jobs\"\ntaskRetention: \"1h\"\n\ncache:\n  mode: \"redis\"\n  ttl: \"10m\"\n  redisKeyPrefix: \"proxq:\"\n\nupstreams:\n  - prefix: \"/api\"\n    url: \"http://api-server:3000\"\n    timeout: \"5m\"\n    maxRetries: 3\n    retryDelay: \"10s\"\n    pathFilter:\n      mode: \"blacklist\"\n      patterns:\n        - \"^/api/auth\"\n        - \"^/api/health\"\n\n  - prefix: \"/uploads\"\n    url: \"http://file-server:9000/storage\"\n    timeout: \"10m\"\n    maxBodySize: 1073741824\n    directProxyThreshold: 0\n    directProxyMode: \"redirect\"\n```\n\n## Headers reference\n\nSet by proxq on responses it generates itself (never on responses proxied verbatim from upstream):\n\n| Header | Value | When |\n|---|---|---|\n| `X-Proxq-Source` | `proxq` | `202` accepted, `502` no upstream match, `500` internal errors, `307` redirects, `404` from job endpoints, reverse-proxy errors |\n| `X-Cache-Status` | `HIT` / `MISS` | On cached responses, when caching is enabled |\n\nAccepted from the client:\n\n| Header | Effect |\n|---|---|\n| `X-Proxq-Timeout` | Go duration string (e.g. `30s`), overrides the matched upstream's `timeout` for that one submitted request. Invalid value → `400`. |\n\nForwarded to upstream on every proxied/queued request: original headers as-is, plus `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`. Hop-by-hop headers (`Connection`, `Keep-Alive`, `Proxy-Authenticate`, `Proxy-Authorization`, `TE`, `Trailers`, `Transfer-Encoding`, `Upgrade`) are stripped per RFC 7230.\n\n## No built-in auth\n\nproxq has no authentication of its own — no API key, bearer token, or allowlist on any endpoint (job submission, status, content, cancel). Put it behind:\n- A reverse proxy doing auth (basic auth, OAuth2 proxy, mTLS) in front of `listenAddress`.\n- Network isolation — bind `listenAddress`/the published port to loopback or an internal-only network, never `0.0.0.0` on a publicly routable host without a fronting proxy.\n\nJob IDs are UUIDv4 (unguessable in practice) but there is **no ownership check** — anyone who can reach the instance and knows/guesses a job ID can poll or cancel it.\n\n## Management / development (operator-side, from the repo)\n\n```bash\nmake dep            # vendor dependencies\nmake lint           # golangci-lint\nmake test           # unit + integration tests (race detector on)\nmake test-coverage  # tests with 90% coverage threshold\nmake build           # docker build\n\n# e2e tests — spins up Redis + upstream + proxq via testcontainers\ncd tests && go test -v -timeout 10m ./...\n```\n\nFile v0.10.10:skill-card.md\n\n## Description: <br>\nproxq helps an agent use a Go, Redis-backed async HTTP proxy queue by submitting HTTP requests, returning job IDs, polling replayed upstream responses, and cancelling jobs against a trusted running instance. <br>\n\nThis skill is ready for commercial/non-commercial use. <br>\n\n## Publisher: <br>\n[psyb0t](https://clawhub.ai/user/psyb0t) <br>\n\n### License/Terms of Use: <br>\nMIT-0 <br>\n\n\n## Use Case: <br>\nDevelopers and operators use this skill to submit, poll, inspect, and cancel proxq jobs for slow backends, webhook relays, large uploads, or long-running processing behind short-timeout reverse proxies. It assumes the proxq service is already deployed and trusted. <br>\n\n### Deployment Geography for Use: <br>\nGlobal <br>\n\n## Known Risks and Mitigations: <br>\nRisk: proxq can proxy requests from its own network position, creating SSRF exposure if untrusted callers or upstreams are allowed. <br>\nMitigation: Use only trusted upstreams, keep the service behind loopback, an internal network, or an authenticated reverse proxy, and do not let untrusted callers choose upstream URLs or prefixes. <br>\nRisk: The proxq service has no built-in authentication or per-job ownership checks. <br>\nMitigation: Protect the service with external authentication or network isolation, and only poll or cancel job IDs supplied by the user or returned from the current workflow. <br>\nRisk: Requests may forward sensitive headers or bodies to configured upstreams. <br>\nMitigation: Send credentials and payloads only when the configured upstream is intended to receive them, and avoid exposing bare proxq endpoints to untrusted clients. <br>\n\n\n## Reference(s): <br>\n- [proxq setup](references/setup.md) <br>\n- [ClawHub skill page](https://clawhub.ai/psyb0t/skills/proxq) <br>\n- [asynq](https://github.com/hibiken/asynq) <br>\n\n\n## Skill Output: <br>\n**Output Type(s):** [guidance, markdown, shell commands, configuration] <br>\n**Output Format:** [Markdown with curl, Docker, Docker Compose, YAML, HTTP, and JSON examples] <br>\n**Output Parameters:** [1D] <br>\n**Other Properties Related to Output:** [Uses PROXQ_URL and requires curl and Docker for the documented workflows.] <br>\n\n## Skill Version(s): <br>\n0.10.10 (source: server release evidence) <br>\n\n## Ethical Considerations: <br>\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>\n\nArchive v0.10.9: 4 files, 9383 bytes\n\nFiles: references/setup.md (8412b), skill-card.md (2311b), SKILL.md (8742b), _meta.json (125b)\n\nFile v0.10.9:SKILL.md\n\n---\nname: proxq\ndescription: Go, Redis-backed async HTTP proxy queue (built on asynq). POST any HTTP request (any method, any path, any body) to a configured upstream, get a job ID back instantly (202), a worker forwards it later, you poll GET /__jobs/{id} for status (queued/running/completed/failed) and GET /__jobs/{id}/content for the replayed upstream response (status/headers/body). DELETE /__jobs/{id} cancels. Path-prefix routing to multiple upstreams, per-upstream timeout/retries/pathFilter, optional response caching (memory or Redis LRU), automatic direct-proxy bypass for WebSocket/chunked/large-body requests. No built-in auth. Use when the user wants to turn a slow/unreliable backend into a fire-and-forget async API, decouple a client from upstream latency, relay webhooks with retries, or queue heavy uploads/processing jobs behind short-timeout reverse proxies.\nhomepage: https://github.com/psyb0t/docker-proxq\nuser-invocable: true\nmetadata:\n  { \"openclaw\": { \"emoji\": \"🦡\", \"primaryEnv\": \"PROXQ_URL\", \"requires\": { \"bins\": [\"curl\", \"docker\"] } } }\npermissions:\n  network: \"outbound HTTP to the configured PROXQ_URL (submit/poll/cancel job calls) — AND proxq itself makes arbitrary outbound HTTP requests to whatever upstream/URL you submit through it, on your behalf. That's an SSRF surface: only submit requests you intend proxq's configured upstreams to receive.\"\n  shell: \"curl + docker/docker-compose invocations shown in setup.md and this file (container lifecycle, request examples) — no other host access\"\n---\n\n# proxq\n\nThe honey badger of HTTP proxies. POST a request, get a job ID back instantly, come back later for the goods. \"I'll get back to you\" as a service — every HTTP request becomes an async job in a Redis-backed queue (via [asynq](https://github.com/hibiken/asynq)).\n\nFor installation, configuration, and container setup, see [references/setup.md](references/setup.md).\n\n## Security & safety\n\n- **This is an SSRF surface by design.** proxq's whole job is to make outbound HTTP requests to an upstream on your behalf — that's not a bug, it's the feature. Anyone who can submit a job through proxq gets proxq's network position to reach whatever `upstreams[].url` is configured (and, via `directProxyMode`/prefix stripping, whatever path/query you tack onto it). Never point an upstream at internal/admin services you wouldn't otherwise expose, and never let untrusted callers choose the upstream prefix or URL.\n- **No built-in authentication or authorization.** proxq ships with zero auth — no API key, no bearer token, no allowlist. Anyone who can reach `PROXQ_URL` can submit jobs, poll any job ID, and cancel any job ID (job IDs are UUIDv4 but there is no ownership check). Front it with a reverse proxy doing auth (basic auth, mTLS, an API gateway) or bind it to loopback/an internal network only — do not expose a bare proxq instance to the open internet.\n- **Trusted upstreams only.** Configure `upstreams[].url` to point only at backends you control or explicitly trust. proxq forwards the full original request (method, headers, body) plus `X-Forwarded-For`/`X-Real-IP`/`X-Forwarded-Proto` — treat the upstream config the same way you'd treat a reverse-proxy target list.\n- **Consumer-only.** This skill talks to an instance you (or your operator) already run and trust. It never provisions, hardens, or reconfigures the server — that's covered in setup.md as an explicit operator step.\n- **Cancelling a job** — `DELETE /__jobs/{id}` best-effort stops an in-flight job and deletes its record (no undo). Per the no-auth point above there's no ownership check, so only cancel a job you submitted or one the user explicitly named — don't guess IDs or bulk-cancel.\n\n## When To Use\n\n- Put an async facade in front of a slow backend so callers get an instant response instead of hanging on a long-running request.\n- Decouple a client from an upstream that occasionally times out — proxq queues the request, retries transport failures automatically, and the client polls at its own pace.\n- Sit behind a CDN/reverse-proxy with a short request timeout while your real backend takes minutes.\n- Relay webhooks without blocking the sender, with configurable retry/backoff.\n- Queue large uploads or long-running processing jobs (video, exports, reports) so the client doesn't hold a connection open.\n- Mix sync and async traffic on one gateway: fast paths (auth, health) bypass the queue via `pathFilter`, slow paths get queued.\n- Cache idempotent (or even non-idempotent-but-repeatable) responses so duplicate requests don't re-hit the upstream.\n\n## When NOT To Use\n\n- True real-time / streaming responses — the whole model is submit-then-poll; there's no push/webhook-back-to-caller notification built in.\n- WebSocket or chunked-transfer traffic that needs the queue semantics — those bypass the queue automatically and just get reverse-proxied straight through (see `directProxyMode` in setup.md), so don't expect a job ID for them.\n- As a public-facing endpoint without your own auth layer in front — proxq has none.\n- Pointing an upstream at anything you don't fully trust — proxq will happily forward arbitrary methods/bodies/headers to it.\n\n## Usage\n\nPoint at a running instance:\n\n```bash\nexport PROXQ_URL=http://localhost:8080\n```\n\nAll job-management endpoints live under `jobsPath` (default `/__jobs`). Every response proxq itself generates (not proxied from upstream) carries `X-Proxq-Source: proxq` — that's how you distinguish a proxq-origin response (job not ready, no upstream match, proxq error) from a real upstream response replayed verbatim.\n\n### Submit a job\n\nAny request that doesn't hit a job endpoint gets routed by longest-prefix match to a configured upstream and queued (unless it qualifies for [direct-proxy bypass](#direct-proxy-bypass) — see setup.md).\n\n```bash\ncurl -s -X POST \"$PROXQ_URL/api/heavy-computation\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer upstream-token\" \\\n  -d '{\"data\": \"lots of it\"}'\n# 202 Accepted, X-Proxq-Source: proxq\n# {\"jobId\": \"550e8400-e29b-41d4-a716-446655440000\"}\n```\n\nIf no upstream prefix matches the request path: `502 Bad Gateway`, `X-Proxq-Source: proxq`.\n\nOptional per-request override header: `X-Proxq-Timeout: <go-duration>` (e.g. `X-Proxq-Timeout: 30s`) overrides the upstream's configured `timeout` for that one request. Invalid value → `400 Bad Request`.\n\n### Poll job status\n\n```bash\ncurl -s \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\"\n```\n\n```json\n{\"id\": \"550e8400-...\", \"status\": \"completed\", \"completedAt\": \"2025-01-01T00:00:00Z\"}\n```\n\nFailed job includes `error`:\n\n```json\n{\"id\": \"550e8400-...\", \"status\": \"failed\", \"error\": \"forward request: dial tcp: connection refused\"}\n```\n\n`status` is one of `queued` (pending/scheduled/aggregating), `running` (active, or waiting on a retry), `completed` (done — response stored, even if upstream returned 4xx/5xx), `failed` (transport broke and retries are exhausted). Unknown job ID → `404 Not Found`, `X-Proxq-Source: proxq`, body `{\"code\":\"NOT_FOUND\",\"message\":\"Not found\"}`.\n\n### Fetch job content (the payoff)\n\nReplays the upstream response exactly — status code, headers, body — as if you'd called upstream directly.\n\n```bash\ncurl -si \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000/content\"\n```\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/json\nX-Custom-Header: from-upstream\n\n{\"result\": \"done\"}\n```\n\nIf upstream returned a 404, you get 404 back too — but **without** `X-Proxq-Source` (it's a real upstream response). If the job isn't done yet, or doesn't exist: `404 Not Found` **with** `X-Proxq-Source: proxq`. That header is the whole disambiguation trick — present means \"proxq talking\", absent means \"upstream talking\".\n\n### Cancel a job\n\n```bash\ncurl -s -X DELETE \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\"\n# {\"status\": \"cancelled\"}\n```\n\nBest-effort: attempts to stop in-flight processing, then deletes the task record. Unknown job ID → `404 Not Found`, `X-Proxq-Source: proxq`. (See [Security & safety](#security--safety) — no ownership check, so only cancel jobs you own.)\n\n### Poll loop example\n\n```bash\nJOB_ID=$(curl -s -X POST \"$PROXQ_URL/api/report\" -d '{}' | jq -r .jobId)\n\nwhile :; do\n  STATUS=$(curl -s \"$PROXQ_URL/__jobs/$JOB_ID\" | jq -r .status)\n  case \"$STATUS\" in\n    completed) curl -s \"$PROXQ_URL/__jobs/$JOB_ID/content\" | jq; break ;;\n    failed)    echo \"job failed\" >&2; break ;;\n    *)         sleep 2 ;;\n  esac\ndone\n```\n\nFor upstream routing rules (prefix matching/stripping), direct-proxy bypass conditions, caching behavior, and the full config reference (env vars, docker run/compose), see [references/setup.md](references/setup.md).\n\nFile v0.10.9:_meta.json\n\n{\n  \"ownerId\": \"kn79dhvmpjng4rp2jjk8k0v5xx80ccbk\",\n  \"slug\": \"proxq\",\n  \"version\": \"0.10.9\",\n  \"publishedAt\": 1785195593461\n}\n\nFile v0.10.9:references/setup.md\n\n# proxq setup\n\n## Quick start (docker compose)\n\n```yaml\nservices:\n  proxq:\n    image: psyb0t/proxq\n    ports:\n      - \"127.0.0.1:8080:8080\"   # bind loopback-only; no built-in auth (see SKILL.md Security & safety)\n    environment:\n      PROXQ_CONFIG: /etc/proxq/config.yaml\n    configs:\n      - source: proxq_config\n        target: /etc/proxq/config.yaml\n    depends_on:\n      - redis\n\n  redis:\n    image: redis:7-alpine\n    restart: unless-stopped\n\nconfigs:\n  proxq_config:\n    content: |\n      listenAddress: \"0.0.0.0:8080\"\n      redis:\n        addr: \"redis:6379\"\n      upstreams:\n        - prefix: \"/\"\n          url: \"http://your-api:3000\"\n```\n\n```bash\ndocker compose up -d\ncurl http://127.0.0.1:8080/__jobs/nonexistent-id   # 404, X-Proxq-Source: proxq → confirms it's up\n```\n\n## Docker run (config file mounted from host)\n\n```bash\ndocker run -d \\\n  --name proxq \\\n  -p 127.0.0.1:8080:8080 \\\n  -e PROXQ_CONFIG=/etc/proxq/config.yaml \\\n  -v \"$(pwd)/config.yaml:/etc/proxq/config.yaml:ro\" \\\n  --link redis \\\n  psyb0t/proxq\n```\n\nRequires a reachable Redis instance (`redis:7-alpine` or any compatible server).\n\n## Config resolution\n\nConfig path is resolved in this order: `--config` CLI flag → `PROXQ_CONFIG` env var → `config.yaml` in the current directory. Everything else lives inside the YAML file itself — there is no per-field env var override for the rest of the settings, so mount/generate the YAML.\n\n| Env var / flag | Purpose |\n|---|---|\n| `--config <path>` | CLI flag, highest priority |\n| `PROXQ_CONFIG` | Path to the YAML config file, read if `--config` is unset |\n| `PUID` / `PGID` | Entrypoint runs the process as this uid:gid via `su-exec` (default `1000:1000`) — container-runtime detail, not app config |\n\n## Config file reference (YAML)\n\n### Global settings\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `listenAddress` | string | `127.0.0.1:8080` | HTTP server bind address |\n| `redis.addr` | string | `127.0.0.1:6379` | Redis server address |\n| `redis.password` | string | `\"\"` | Redis password |\n| `redis.db` | int | `0` | Redis database number |\n| `queue` | string | `default` | asynq queue name |\n| `concurrency` | int | `10` | Concurrent workers hitting upstream |\n| `jobsPath` | string | `/__jobs` | Base path for the jobs API endpoints |\n| `taskRetention` | duration | `1h` | How long completed/failed jobs stay in Redis before eviction |\n\nDuration values use Go syntax: `30s`, `5m`, `1h`, `1h30m`.\n\n### Upstreams (`upstreams[]`)\n\nRouted by longest path-prefix match; the matched prefix is stripped before forwarding.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `prefix` | string | **required** | URL path prefix for routing. Stripped before forwarding. |\n| `url` | string | **required** | Upstream server URL. May include a path (e.g. `http://api:3000/v2`). |\n| `timeout` | duration | `5m` | Per-upstream request timeout (overridable per-request via `X-Proxq-Timeout`) |\n| `maxRetries` | int | `0` | Retry attempts on transport failure. `0` = no retries. |\n| `retryDelay` | duration | `0` | Fixed delay between retries. `0` = exponential backoff (`n^4` seconds: 1s, 16s, 81s, ~4m, ~10m for attempts 1-5). |\n| `maxBodySize` | int (bytes) | `10485760` (10 MB) | Max request body buffered into the queue |\n| `directProxyThreshold` | int (bytes) | `10485760` (10 MB) | Body size above which requests bypass the queue entirely. `0` disables (always queue regardless of size, up to `maxBodySize`). |\n| `directProxyMode` | string | `proxy` | How bypassed requests reach upstream: `proxy` (reverse-proxied, client never sees upstream URL) or `redirect` (`307 Temporary Redirect` to the upstream URL) |\n| `cacheKeyExcludeHeaders` | list[string] | `[]` (defaults apply) | Headers excluded from the cache key. Empty list = built-in defaults (`X-Request-ID`, `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`). Setting this **replaces** the defaults entirely. |\n| `pathFilter.mode` | string | `blacklist` | `blacklist`: matching paths bypass the queue. `whitelist`: only matching paths get queued. |\n| `pathFilter.patterns` | list[string] | `[]` | Regex patterns matched against the request path. |\n\n### Cache (`cache`)\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `cache.mode` | string | `none` | `none`, `memory` (in-process LRU), or `redis` (shared, same Redis instance as the queue) |\n| `cache.ttl` | duration | `5m` | Freshness window |\n| `cache.maxEntries` | int | `10000` | Max entries for in-memory LRU mode |\n| `cache.redisKeyPrefix` | string | `proxq:` | Key prefix for Redis cache mode, avoids colliding with job data |\n\nCache rules: any HTTP method can be cached (same body → cache hit, different body → miss); only 2xx upstream responses are cached; cache key = `sha256(method + url + headers + body)` with volatile headers excluded per `cacheKeyExcludeHeaders`. Responses carry `X-Cache-Status: HIT` or `MISS`.\n\n### Direct proxy bypass — checked in this order\n\n| Condition | Why |\n|---|---|\n| WebSocket (`Connection: upgrade` + `Upgrade: websocket`) | Persistent bidirectional, can't queue |\n| Path filter match (per-upstream `pathFilter`) | Explicit opt-out |\n| Chunked transfer (`Transfer-Encoding: chunked`) | Size unknown |\n| Body over `directProxyThreshold` | Avoid buffering huge uploads into Redis |\n\n### Validation (fails startup if violated)\n\n- At least one upstream is required.\n- Every upstream needs both `prefix` and `url`.\n- Single upstream may use `prefix: \"/\"` (catch-all). Multiple upstreams may **not** include `prefix: \"/\"` — too ambiguous.\n- No nested prefixes (`/api` and `/api/v2` together is an error).\n- No upstream prefix may conflict with `jobsPath` (e.g. an upstream at `/__jobs` when `jobsPath` is `/__jobs`).\n- All `pathFilter.patterns` must be valid regexes.\n\n### Example config\n\n```yaml\nlistenAddress: \"0.0.0.0:8080\"\n\nredis:\n  addr: \"redis:6379\"\n  password: \"\"\n  db: 0\n\nqueue: \"default\"\nconcurrency: 10\njobsPath: \"/__jobs\"\ntaskRetention: \"1h\"\n\ncache:\n  mode: \"redis\"\n  ttl: \"10m\"\n  redisKeyPrefix: \"proxq:\"\n\nupstreams:\n  - prefix: \"/api\"\n    url: \"http://api-server:3000\"\n    timeout: \"5m\"\n    maxRetries: 3\n    retryDelay: \"10s\"\n    pathFilter:\n      mode: \"blacklist\"\n      patterns:\n        - \"^/api/auth\"\n        - \"^/api/health\"\n\n  - prefix: \"/uploads\"\n    url: \"http://file-server:9000/storage\"\n    timeout: \"10m\"\n    maxBodySize: 1073741824\n    directProxyThreshold: 0\n    directProxyMode: \"redirect\"\n```\n\n## Headers reference\n\nSet by proxq on responses it generates itself (never on responses proxied verbatim from upstream):\n\n| Header | Value | When |\n|---|---|---|\n| `X-Proxq-Source` | `proxq` | `202` accepted, `502` no upstream match, `500` internal errors, `307` redirects, `404` from job endpoints, reverse-proxy errors |\n| `X-Cache-Status` | `HIT` / `MISS` | On cached responses, when caching is enabled |\n\nAccepted from the client:\n\n| Header | Effect |\n|---|---|\n| `X-Proxq-Timeout` | Go duration string (e.g. `30s`), overrides the matched upstream's `timeout` for that one submitted request. Invalid value → `400`. |\n\nForwarded to upstream on every proxied/queued request: original headers as-is, plus `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`. Hop-by-hop headers (`Connection`, `Keep-Alive`, `Proxy-Authenticate`, `Proxy-Authorization`, `TE`, `Trailers`, `Transfer-Encoding`, `Upgrade`) are stripped per RFC 7230.\n\n## No built-in auth\n\nproxq has no authentication of its own — no API key, bearer token, or allowlist on any endpoint (job submission, status, content, cancel). Put it behind:\n- A reverse proxy doing auth (basic auth, OAuth2 proxy, mTLS) in front of `listenAddress`.\n- Network isolation — bind `listenAddress`/the published port to loopback or an internal-only network, never `0.0.0.0` on a publicly routable host without a fronting proxy.\n\nJob IDs are UUIDv4 (unguessable in practice) but there is **no ownership check** — anyone who can reach the instance and knows/guesses a job ID can poll or cancel it.\n\n## Management / development (operator-side, from the repo)\n\n```bash\nmake dep            # vendor dependencies\nmake lint           # golangci-lint\nmake test           # unit + integration tests (race detector on)\nmake test-coverage  # tests with 90% coverage threshold\nmake build           # docker build\n\n# e2e tests — spins up Redis + upstream + proxq via testcontainers\ncd tests && go test -v -timeout 10m ./...\n```\n\nFile v0.10.9:skill-card.md\n\n## Description: <br>\nproxq helps agents use a self-run Redis-backed asynchronous HTTP proxy queue to submit requests, poll job status, fetch replayed upstream responses, and cancel jobs. <br>\n\nThis skill is ready for commercial/non-commercial use. <br>\n\n## Publisher: <br>\n[psyb0t](https://clawhub.ai/user/psyb0t) <br>\n\n### License/Terms of Use: <br>\nMIT-0 <br>\n\n\n## Use Case: <br>\nDevelopers and operators use this skill to interact with a trusted proxq instance when they need fire-and-forget HTTP request handling, webhook relays, retries, queued long-running work, or polling-based response retrieval behind short-timeout clients and reverse proxies. <br>\n\n### Deployment Geography for Use: <br>\nGlobal <br>\n\n## Known Risks and Mitigations: <br>\nRisk: A reachable proxq instance can submit outbound requests from its network position and expose proxy behavior to callers. <br>\nMitigation: Operate only trusted proxq instances, keep them behind authentication or loopback/internal networking, and configure upstreams only for services you trust. <br>\nRisk: Requests may include secrets or sensitive bodies that pass through proxq, Redis storage, and the configured upstream. <br>\nMitigation: Avoid sensitive payloads unless the proxq instance, Redis storage, and upstream are all under appropriate control. <br>\n\n\n## Reference(s): <br>\n- [proxq setup](references/setup.md) <br>\n- [proxq skill page](https://clawhub.ai/psyb0t/skills/proxq) <br>\n- [asynq](https://github.com/hibiken/asynq) <br>\n\n\n## Skill Output: <br>\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance] <br>\n**Output Format:** [Markdown guidance with curl, Docker, Docker Compose, YAML, HTTP, and JSON examples] <br>\n**Output Parameters:** [1D] <br>\n**Other Properties Related to Output:** [Guidance assumes an existing trusted proxq instance identified by PROXQ_URL and may include job submission, polling, content retrieval, cancellation, and setup commands.] <br>\n\n## Skill Version(s): <br>\n0.10.9 (source: server release metadata) <br>\n\n## Ethical Considerations: <br>\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>\n\nArchive v0.10.8: 4 files, 9573 bytes\n\nFiles: references/setup.md (8412b), skill-card.md (2802b), SKILL.md (8742b), _meta.json (125b)\n\nFile v0.10.8:SKILL.md\n\n---\nname: proxq\ndescription: Go, Redis-backed async HTTP proxy queue (built on asynq). POST any HTTP request (any method, any path, any body) to a configured upstream, get a job ID back instantly (202), a worker forwards it later, you poll GET /__jobs/{id} for status (queued/running/completed/failed) and GET /__jobs/{id}/content for the replayed upstream response (status/headers/body). DELETE /__jobs/{id} cancels. Path-prefix routing to multiple upstreams, per-upstream timeout/retries/pathFilter, optional response caching (memory or Redis LRU), automatic direct-proxy bypass for WebSocket/chunked/large-body requests. No built-in auth. Use when the user wants to turn a slow/unreliable backend into a fire-and-forget async API, decouple a client from upstream latency, relay webhooks with retries, or queue heavy uploads/processing jobs behind short-timeout reverse proxies.\nhomepage: https://github.com/psyb0t/docker-proxq\nuser-invocable: true\nmetadata:\n  { \"openclaw\": { \"emoji\": \"🦡\", \"primaryEnv\": \"PROXQ_URL\", \"requires\": { \"bins\": [\"curl\", \"docker\"] } } }\npermissions:\n  network: \"outbound HTTP to the configured PROXQ_URL (submit/poll/cancel job calls) — AND proxq itself makes arbitrary outbound HTTP requests to whatever upstream/URL you submit through it, on your behalf. That's an SSRF surface: only submit requests you intend proxq's configured upstreams to receive.\"\n  shell: \"curl + docker/docker-compose invocations shown in setup.md and this file (container lifecycle, request examples) — no other host access\"\n---\n\n# proxq\n\nThe honey badger of HTTP proxies. POST a request, get a job ID back instantly, come back later for the goods. \"I'll get back to you\" as a service — every HTTP request becomes an async job in a Redis-backed queue (via [asynq](https://github.com/hibiken/asynq)).\n\nFor installation, configuration, and container setup, see [references/setup.md](references/setup.md).\n\n## Security & safety\n\n- **This is an SSRF surface by design.** proxq's whole job is to make outbound HTTP requests to an upstream on your behalf — that's not a bug, it's the feature. Anyone who can submit a job through proxq gets proxq's network position to reach whatever `upstreams[].url` is configured (and, via `directProxyMode`/prefix stripping, whatever path/query you tack onto it). Never point an upstream at internal/admin services you wouldn't otherwise expose, and never let untrusted callers choose the upstream prefix or URL.\n- **No built-in authentication or authorization.** proxq ships with zero auth — no API key, no bearer token, no allowlist. Anyone who can reach `PROXQ_URL` can submit jobs, poll any job ID, and cancel any job ID (job IDs are UUIDv4 but there is no ownership check). Front it with a reverse proxy doing auth (basic auth, mTLS, an API gateway) or bind it to loopback/an internal network only — do not expose a bare proxq instance to the open internet.\n- **Trusted upstreams only.** Configure `upstreams[].url` to point only at backends you control or explicitly trust. proxq forwards the full original request (method, headers, body) plus `X-Forwarded-For`/`X-Real-IP`/`X-Forwarded-Proto` — treat the upstream config the same way you'd treat a reverse-proxy target list.\n- **Consumer-only.** This skill talks to an instance you (or your operator) already run and t\n\nArchive v0.10.7: 4 files, 9568 bytes\n\nFiles: references/setup.md (8412b), skill-card.md (2797b), SKILL.md (8742b), _meta.json (125b)\n\nArchive v0.10.6: 4 files, 9515 bytes\n\nFiles: references/setup.md (8412b), skill-card.md (2695b), SKILL.md (8742b), _meta.json (125b)","readmeExcerpt":"Skill: proxq Owner: psyb0t Summary: Go, Redis-backed async HTTP proxy queue (built on asynq). POST any HTTP request (any method, any path, any body) to a configured upstream, get a job ID back instantly (202), a worker forwards it later, you poll GET /__jobs/{id} for status (queued/running/completed/failed) and GET /__jobs/{id}/content for the replayed upstream response (status/headers/body). DELETE /__jobs/{id} canc","codeSnippets":[],"executableExamples":[{"language":"bash","snippet":"export PROXQ_URL=http://localhost:8080"},{"language":"bash","snippet":"curl -s -X POST \"$PROXQ_URL/api/heavy-computation\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer $UPSTREAM_TOKEN\" \\\n  -d '{\"data\": \"lots of it\"}'"},{"language":"bash","snippet":"curl -s -X POST \"$PROXQ_URL/api/heavy-computation\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer $UPSTREAM_TOKEN\" \\\n  -d '{\"data\": \"lots of it\"}'\n# 202 Accepted, X-Proxq-Source: proxq\n# {\"jobId\": \"550e8400-e29b-41d4-a716-446655440000\"}"},{"language":"bash","snippet":"curl -s \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\""},{"language":"bash","snippet":"curl -s \"$PROXQ_URL/__jobs/550e8400-e29b-41d4-a716-446655440000\""},{"language":"json","snippet":"{\"id\": \"550e8400-...\", \"status\": \"completed\", \"completedAt\": \"2025-01-01T00:00:00Z\"}"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: proxq\ndescription: Go, Redis-backed async HTTP proxy queue (built on asynq). POST any HTTP request (any method, any path, any body) to a configured upstream, get a job ID back instantly (202), a worker forwards it later, you poll GET /__jobs/{id} for status (queued/running/completed/failed) and GET /__jobs/{id}/content for the replayed upstream response (status/headers/body). DELETE /__jobs/{id} cancels. Path-prefix routing to multiple upstreams, per-upstream timeout/retries/pathFilter, optional response caching (memory or Redis LRU), automatic direct-proxy bypass for WebSocket/chunked/large-body requests. No built-in auth. Use when the user wants to turn a slow/unreliable backend into a fire-and-forget async API, decouple a client from upstream latency, relay webhooks with retries, or queue heavy uploads/processing jobs behind short-timeout reverse proxies.\nhomepage: https://github.com/psyb0t/docker-proxq\nuser-invocable: true\nmetadata:\n  { \"openclaw\": { \"emoji\": \"🦡\", \"primaryEnv\": \"PROXQ_URL\", \"requires\": { \"bins\": [\"curl\", \"docker\"] } } }\npermissions:\n  network: \"outbound HTTP to the configured PROXQ_URL (submit/poll/cancel job calls) — AND proxq itself makes arbitrary outbound HTTP requests to whatever upstream/URL you submit through it, on your behalf. That's an SSRF surface: only submit requests you intend proxq's configured upstreams to receive.\"\n  shell: \"curl + docker/docker-compose invocations shown in setup.md and this file (container lifecycle, request examples) — no other host access\"\n---\n\n# proxq\n\nThe honey badger of HTTP proxies. POST a request, get a job ID back instantly, come back later for the goods. \"I'll get back to you\" as a service — every HTTP request becomes an async job in a Redis-backed queue (via [asynq](https://github.com/hibiken/asynq)).\n\nFor installation, configuration, and container setup, see [references/setup.md](references/setup.md).\n\n## Security & safety\n\n- **This is an SSRF surface by design.** proxq's whole job is to make outbound HTTP requests to an upstream on your behalf — that's not a bug, it's the feature. Anyone who can submit a job through proxq gets proxq's network position to reach whatever `upstreams[].url` is configured (and, via `directProxyMode`/prefix stripping, whatever path/query you tack onto it). Never point an upstream at internal/admin services you wouldn't otherwise expose, and never let untrusted callers choose the upstream prefix or URL.\n- **No built-in authentication or authorization.** proxq ships with zero auth — no API key, no bearer token, no allowlist. Anyone who can reach `PROXQ_URL` can submit jobs, poll any job ID, and cancel any job ID (job IDs are UUIDv4 but there is no ownership check). Front it with a reverse proxy doing auth (basic auth, mTLS, an API gateway) or bind it to loopback/an internal network only — do not expose a bare proxq instance to the open internet.\n- **Trusted upstreams only.** Configure `upstreams[].url` to point only at backends you control or "},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn79dhvmpjng4rp2jjk8k0v5xx80ccbk\",\n  \"slug\": \"proxq\",\n  \"version\": \"0.11.4\",\n  \"publishedAt\": 1791645929937\n}"},{"path":"references/setup.md","content":"# proxq setup\n\n## Quick start (docker compose)\n\n```yaml\nservices:\n  proxq:\n    image: psyb0t/proxq\n    ports:\n      - \"127.0.0.1:8080:8080\"   # bind loopback-only; no built-in auth (see SKILL.md Security & safety)\n    environment:\n      PROXQ_CONFIG: /etc/proxq/config.yaml\n    configs:\n      - source: proxq_config\n        target: /etc/proxq/config.yaml\n    depends_on:\n      - redis\n\n  redis:\n    image: redis:7-alpine\n    restart: unless-stopped\n\nconfigs:\n  proxq_config:\n    content: |\n      listenAddress: \"0.0.0.0:8080\"\n      redis:\n        addr: \"redis:6379\"\n      upstreams:\n        - prefix: \"/\"\n          url: \"http://your-api:3000\"\n```\n\n```bash\ndocker compose up -d\ncurl http://127.0.0.1:8080/__jobs/nonexistent-id   # 404, X-Proxq-Source: proxq → confirms it's up\n```\n\n## Docker run (config file mounted from host)\n\n```bash\ndocker run -d \\\n  --name proxq \\\n  -p 127.0.0.1:8080:8080 \\\n  -e PROXQ_CONFIG=/etc/proxq/config.yaml \\\n  -v \"$(pwd)/config.yaml:/etc/proxq/config.yaml:ro\" \\\n  --link redis \\\n  psyb0t/proxq\n```\n\nRequires a reachable Redis instance (`redis:7-alpine` or any compatible server).\n\n## Config resolution\n\nConfig path is resolved in this order: `--config` CLI flag → `PROXQ_CONFIG` env var → `config.yaml` in the current directory. Everything else lives inside the YAML file itself — there is no per-field env var override for the rest of the settings, so mount/generate the YAML.\n\n| Env var / flag | Purpose |\n|---|---|\n| `--config <path>` | CLI flag, highest priority |\n| `PROXQ_CONFIG` | Path to the YAML config file, read if `--config` is unset |\n\nThe published image runs the process as a fixed non-root user (`proxq`) baked into the image at build time — there is no `PUID`/`PGID` override.\n\n## Config file reference (YAML)\n\n### Global settings\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `listenAddress` | string | `127.0.0.1:8080` | HTTP server bind address |\n| `redis.addr` | string | `127.0.0.1:6379` | Redis server address |\n| `redis.username` | string | `\"\"` | Redis ACL username. Empty uses the default user |\n| `redis.password` | string | `\"\"` | Redis password |\n| `redis.db` | int | `0` | Redis database number |\n| `queue` | string | `default` | asynq queue name |\n| `concurrency` | int | `10` | Concurrent workers hitting upstream |\n| `jobsPath` | string | `/__jobs` | Base path for the jobs API endpoints |\n| `taskRetention` | duration | `1h` | How long completed/failed jobs stay in Redis before eviction |\n\nDuration values use Go syntax: `30s`, `5m`, `1h`, `1h30m`.\n\n### Upstreams (`upstreams[]`)\n\nRouted by longest path-prefix match; the matched prefix is stripped before forwarding.\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `prefix` | string | **required** | URL path prefix for routing. Stripped before forwarding. |\n| `url` | string | **required** | Upstream server URL. May include a path (e.g. `http://api:3000/v2`). |\n| `timeout` | duration | `5m` | Per-upstream request timeout (overridable per-request via"},{"path":"skill-card.md","content":"## Description:\n\nHelps developers submit HTTP requests to a trusted proxq instance for asynchronous forwarding, then poll results or cancel jobs.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[psyb0t](https://clawhub.ai/user/psyb0t)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and engineers use this skill to submit requests to a proxq instance, monitor queued work, retrieve upstream responses, and cancel their own jobs when needed.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: An internet-exposed proxq instance has no built-in authentication or job ownership checks.\n\nMitigation: Bind it to loopback or an internal network, require authentication or mTLS at the edge, and cancel only jobs you own.\n\nRisk: Submitted requests can reach configured upstreams from the proxy's network position.\n\nMitigation: Configure only trusted upstreams and submit only requests intended for those services.\n\nRisk: Unpinned container images can change between deployments.\n\nMitigation: Pin Docker images before production use.\n\n## Reference(s):\n\n- [proxq ClawHub release](https://clawhub.ai/psyb0t/skills/proxq)\n- [proxq setup and configuration](references/setup.md)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration guidance, Text]\n\n**Output Format:** [Markdown with shell and YAML examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Job IDs, status, and upstream responses are returned by the configured proxq instance.]\n\n## Skill Version(s):\n\n0.11.4 (source: ClawHub release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":1623,"uniquenessScore":42,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T01:48:59.807Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T01:48:59.807Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-11T03:54:57.260Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}