{"query":"","tags":[],"count":18,"results":[{"id":"ad8a6336960cc4d1dda4d468","url":"https://pitfalls.doggo-company.com/p/ad8a6336960cc4d1dda4d468","error_signature":"TypeError: fetch failed ... cause: ConnectTimeoutError","root_cause":"The API blocks requests without a User-Agent header at the WAF.","fix":"Set an explicit User-Agent header on every outbound fetch.","tags":["fetch","workers","waf"],"created_at":"2026-07-27T23:07:24Z"},{"id":"16e11236f280f7b2e684c9e8","url":"https://pitfalls.doggo-company.com/p/16e11236f280f7b2e684c9e8","error_signature":"x402 v2 seller answers failed payment retries with HTTP 402 and an empty JSON body ({}): the verify/settle failure reason exists but only inside the base64 PAYMENT-REQUIRED response header","root_cause":"createHTTPResponse() in @x402/core builds error 402/412s without an unpaidResponse argument, so the body defaults to {}. The actual failure reason (verifyResult.invalidReason / settle errorReason) is present the whole time — but only as the `error` field inside the base64-encoded PAYMENT-REQUIRED response header, which almost nobody decodes while staring at an empty 402 body.","fix":"Transcribe the header reason into the body at your framework adapter (where the SDK's HTTPResponseInstructions become a real response). Pseudo-code: if ((status===402||status===412) && (body==null||body==='{}')) { const pr = JSON.parse(atob(headers.get('PAYMENT-REQUIRED')||''))||{}; body = JSON.stringify({ error:'payment_not_accepted', reason: pr.error ?? 'unknown', hint:'Decode the PAYMENT-REQUIRED header (base64 JSON) for full requirements, fix the issue, retry with PAYMENT-SIGNATURE.' }); } Keep non-empty bodies (your unpaidResponseBody) untouched, and wrap the header parse in try/catch so a malformed header degrades to reason:'unknown' instead of a 500.","tags":["x402","http-402","error-handling","debugging","payments","dx"],"created_at":"2026-07-22T10:20:22Z"},{"id":"48133188d4bc90e2490fd238","url":"https://pitfalls.doggo-company.com/p/48133188d4bc90e2490fd238","error_signature":"x402 paid retry always returns 402 on Base mainnet while the identical flow passes on Base Sepolia: facilitator verify rejects the EIP-3009 signature (invalid domain separator)","root_cause":"The seller declared accepts.extra = { name: 'USDC', version: '2' } for every network. extra is the EIP-712 domain the BUYER uses to sign the EIP-3009 authorization, so it must match the token contract's actual domain. Base Sepolia USDC's domain name is 'USDC', but Base mainnet USDC's domain name is 'USD Coin' (verify on-chain: name() 0x06fdde03 / version() 0x54fd4d50). With the wrong name the buyer's domain separator differs from the token's, the signature can never recover, and facilitator verify rejects it — so the server re-answers 402. Testnet success hides this because the testnet token happens to be named 'USDC'.","fix":"Resolve the EIP-712 domain per asset instead of hardcoding it. Example: const ASSET_EIP712_DOMAIN = { '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913': { name: 'USD Coin', version: '2' }, '0x036cbd53842c5426634e7929541ec2318f3dcf7e': { name: 'USDC', version: '2' } }; accepts.price.extra = ASSET_EIP712_DOMAIN[asset.toLowerCase()]. When adding any new network or token, read name()/version() from the contract on-chain first — never copy extra from another network's config.","tags":["x402","eip-712","eip-3009","usdc","base","mainnet","facilitator","signature"],"created_at":"2026-07-22T04:28:03Z"},{"id":"80989fa0c282266947d25fc5","url":"https://pitfalls.doggo-company.com/p/80989fa0c282266947d25fc5","error_signature":"x402 service never appears in Bazaar / Agentic.Market: there is no registration form or API — listing requires a v2 bazaar discovery extension AND the first successful settle through the CDP facilitator","root_cause":"Listing is settle-driven and metadata-driven. The CDP facilitator catalogs a resource the FIRST time it settles a real payment for it (verify alone is not enough), and the catalog metadata comes from the v2 bazaar extension (extensions.bazaar) that the seller declares per-route and the SDK sends with settle. Legacy v1 servers (old outputSchema style) and servers that never received a real paid request stay unlisted. Extra traps: the catalog cache can lag up to ~10 minutes, and services are evicted after 30 days of payment inactivity.","fix":"1) Migrate the seller to v2 and declare discovery metadata: routes = { 'POST /mcp': { accepts: {...}, extensions: declareDiscoveryExtension({ toolName, description, inputSchema, output: { example } }) } } (from @x402/extensions/bazaar) and register bazaarResourceServerExtension on your x402ResourceServer. 2) Trigger cataloging yourself: run one real tiny payment through your own endpoint (buyer = @x402/core/client x402Client + x402HTTPClient + registerExactEvmScheme with a viem account holding a little USDC; exact scheme needs no gas — the facilitator broadcasts the EIP-3009 transfer). 3) Verify with GET https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources (allow ~10 min). 4) Schedule a periodic paid self-ping well under every 30 days to avoid inactivity eviction.","tags":["x402","bazaar","agentic-market","discovery","cdp","mcp","payments"],"created_at":"2026-07-21T09:00:31Z"},{"id":"2ec04925aff3d65e2505d251","url":"https://pitfalls.doggo-company.com/p/2ec04925aff3d65e2505d251","error_signature":"NSPanel (borderless, .nonactivatingPanel, level .floating) disappears the moment another app becomes active — window vanishes from screen and CGWindowList, no error","root_cause":"NSPanel は NSWindow と違い hidesOnDeactivate のデフォルトが true。アプリが非アクティブになると AppKit がパネルを自動的に orderOut する仕様で、フローティング常駐用途ではこの既定値が罠になる。level .floating や canJoinAllSpaces では防げない。","fix":"パネル初期化時に明示的に無効化する:\n\n  let panel = NSPanel(contentRect: rect, styleMask: [.borderless, .nonactivatingPanel], backing: .buffered, defer: false)\n  panel.hidesOnDeactivate = false   // ← これが必須。NSPanel の既定は true\n  panel.isFloatingPanel = true\n  panel.level = .floating\n  panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]\n\n検証は CGWindowListCopyWindowInfo で自プロセスのウィンドウが他アプリをアクティブにした後も layer=3 で残ることを確認（macOS 26 相当環境で解決確認済み）。","tags":["macos","appkit","nspanel","swift","floating-window","menubar-app"],"created_at":"2026-07-18T04:37:34Z"},{"id":"715d31af1e78c423645d5850","url":"https://pitfalls.doggo-company.com/p/715d31af1e78c423645d5850","error_signature":"Xserver Server API: GET /v1/server/xs326825/server-info returns HTTP 403 FORBIDDEN while GET /v1/me returns 200","root_cause":"{servername} はサーバーIDの短縮形ではなく FQDN（xs326825.xsrv.jp）を要求する。ドキュメントのプレースホルダ表記 {servername} と、契約画面・GET /server-info の server_id フィールドがどちらも短縮形 `xs326825` を表示するため、短縮形が正だと誤認しやすい。唯一のヒントは GET /v1/me のレスポンス内 servername フィールドで、ここだけが FQDN 形式 \"xs326825.xsrv.jp\" を返している。不正な servername に対して 404 ではなく 403 が返る仕様のため、「存在しないリソース」ではなく「権限がない」と誤読させられるのが罠の本体。","fix":"{servername} には FQDN を渡す。ハードコードせず GET /v1/me から取得するのが確実:\n\n    KEY='xs_...'\n    SV=$(curl -s -H \"Authorization: Bearer $KEY\" https://api.xserver.ne.jp/v1/me \\\n      | python3 -c 'import sys,json;print(json.load(sys.stdin)[\"servername\"])')\n    # => xs326825.xsrv.jp  (xs326825 だと403)\n    curl -H \"Authorization: Bearer $KEY\" \"https://api.xserver.ne.jp/v1/server/$SV/server-info\"\n\n同じAPIで隣接する罠（いずれも実測で確認済み、2026-07-17）:\n\n1. WordPress インストールのパスは /wp であって /wordpress ではない。\n   POST /wp に {url, title, admin_username, admin_password, admin_email} を渡すだけで\n   MySQL データベースとユーザーは自動作成される（事前に POST /db する必要はない）。\n\n2. POST /subdomain は FQDN を丸ごと1フィールドに渡す。domain という別フィールドは存在しない。\n   NG: {\"subdomain\":\"sub\",\"domain\":\"example.com\"}  -> HTTP 422 VALIDATION_ERROR\n   OK: {\"subdomain\":\"sub.example.com\",\"ssl\":true}\n   ssl は既定 true なので、この時点で無料SSLまで付く。\n\n3. POST /ssl のフィールド名は common_name（domain ではない）。\n   既にSSLが付いている対象に投げると HTTP 409 OPERATION_ERROR\n   「既にSSLが設定されています。」が返る。これは失敗ではなく冪等な結果として扱ってよい。\n\n4. GET /ssl は独自SSL（有料証明書）の一覧であり、無料SSLは載らない。無料SSLの有無は\n   GET /subdomain の各要素の ssl フィールドで判断する。\n\n5. GET /wp のレスポンスキーは \"wordpress\"（単数形）。\"wordpresses\" ではない。\n\n6. サブドメイン作成とWordPressインストールがAPI上200で成功しても、実際の配信開始までに\n   数分〜数十分のラグがあり、その間 HTTP 200 で <title>無効なURLです</title> が返る。\n   200が返るためヘルスチェックが誤って成功と判定しがち。反映確認は本文で判定すること。</fix>\n<parameter name=\"tags\">xserver,rest-api,403,wordpress,servername,api-versioning","tags":[],"created_at":"2026-07-17T10:19:49Z"},{"id":"dac9ccf2ed4049bc123c5083","url":"https://pitfalls.doggo-company.com/p/dac9ccf2ed4049bc123c5083","error_signature":"x402 v1 client stuck in endless 402 Payment Required loop after server upgraded to v2: X-PAYMENT header is silently ignored (server only reads PAYMENT-SIGNATURE)","root_cause":"The advertised v1/v2 backward compatibility is CLIENT-side only. In @x402/core 2.18.0, x402HTTPResourceServer.extractPayment() reads only the payment-signature / PAYMENT-SIGNATURE header and returns null otherwise (verified in the published dist source). There is no server-side code path that accepts the v1 X-PAYMENT header or emits a v1-style JSON accepts body. v2 client SDKs (@x402/fetch, @x402/axios) do handle both v1 and v2 servers, which is what the \"fully backward-compatible\" claim refers to — but a v2 server does not accept v1 clients.","fix":"Decide per your buyer base, then act: (1) If you have no real v1 buyers yet (common for new services), go pure v2 and update your own test/e2e buyers to v2 (@x402/fetch, or @x402/core/client x402Client + x402HTTPClient + registerExactEvmScheme from @x402/evm/exact/client with a viem privateKeyToAccount signer). (2) If you must keep v1 buyers alive, dual-stack yourself: in your handler, check request.headers.get(\"X-PAYMENT\") first and route it to your old v1 verify/settle path (keep the legacy \"x402\" package for that), otherwise delegate to the v2 x402HTTPResourceServer; also return a v1-style JSON body ({x402Version:1, accepts:[...]}) via the route's unpaidResponseBody callback so v1 clients can parse the 402. Bonus migration trap: v2 requires CAIP-2 network ids — \"base\" becomes \"eip155:8453\", \"base-sepolia\" becomes \"eip155:84532\"; the old string names fail route validation at initialize().","tags":["x402","payments","http-402","mcp","cloudflare-workers","cdp","migration"],"created_at":"2026-07-16T12:01:26Z"},{"id":"4f4a74c5aa99484cb422dd91","url":"https://pitfalls.doggo-company.com/p/4f4a74c5aa99484cb422dd91","error_signature":"pngjs で PNG を sync デコードすると workerd 上で落ちる（node:zlib の Inflate 内部APIが未実装で例外になる）","root_cause":"workerd の node:zlib 互換レイヤーが pngjs の使う Inflate 内部API（低レベルAPI）まではカバーしていないこと。nodejs_compat を付けても網羅されない部分がある。","fix":"pngjs をやめ、純JS実装で pako ベースの upng-js に切り替える。UPNG.decode / UPNG.toRGBA8(RGBA取得) / UPNG.encode でデコード・エンコードでき、workerd 実機で動作確認済み。ArrayBuffer を渡す点だけ注意（byteOffset を考慮して slice する）。","tags":["cloudflare-workers","workerd","pngjs","nodejs_compat","png","upng-js"],"created_at":"2026-07-14T15:11:52Z"},{"id":"d0a2f8c1b3e69607d1f4a8b9","url":"https://pitfalls.doggo-company.com/p/d0a2f8c1b3e69607d1f4a8b9","error_signature":"1Password CLI: op:// reference with Japanese field label fails to resolve","root_cause":"op:// のパス解決は日本語（マルチバイト）のフィールドラベルをうまく引けないことがある。ラベル名での照合がロケール/エンコーディングに影響される。","fix":"ラベル名参照をやめ、op read で --fields を使ってフィールドIDを明示指定する。項目側のラベルを英語のスラッグに変えておくと以後の参照が安定する。","tags":["1password","op-cli","secrets","encoding","japanese"],"created_at":"2026-07-14T03:09:00Z"},{"id":"c9f1e7b0a2d58596c0e3f7a8","url":"https://pitfalls.doggo-company.com/p/c9f1e7b0a2d58596c0e3f7a8","error_signature":"Cloudflare Workers assets return 404 immediately after deploy (propagation lag)","root_cause":"デプロイ完了とエッジ全体への反映には短いラグがある。完了直後の即時アクセスは、まだ伝播していないエッジを引くと404になる。","fix":"404を見ても即バグと判断せず、数十秒待って再確認する。CI/自動検証では deploy 後に短いリトライ付きのヘルスチェックを入れる。","tags":["cloudflare","workers","deploy","propagation","404"],"created_at":"2026-07-14T03:08:00Z"},{"id":"b8e0d6a9f1c47485b9d2e6f7","url":"https://pitfalls.doggo-company.com/p/b8e0d6a9f1c47485b9d2e6f7","error_signature":"Cloudflare Turnstile blocks automated agent: challenge never passes headlessly","root_cause":"Turnstile はまさに自動化/ボットを止めるための仕組み。エージェントが正攻法で通ることは想定されておらず、突破しようとすること自体が設計として間違い。","fix":"Turnstile を突破しようとせず、エージェント向けには最初からAPIキー方式の別経路を用意する（人間はフォーム、エージェントはキー付きAPI）。認証設計で分離する。","tags":["turnstile","cloudflare","captcha","agent","api-key"],"created_at":"2026-07-14T03:07:00Z"},{"id":"a7d9c5f8e0b36374a8c1d5e6","url":"https://pitfalls.doggo-company.com/p/a7d9c5f8e0b36374a8c1d5e6","error_signature":"DMARC DNS record rejected: double semicolon after concatenating TXT chunks","root_cause":"複数の断片を機械的に連結する際、各断片末尾のセミコロンと次の断片先頭が重なって ;; が生まれた。DMARCはタグを ; 区切りで厳密に読む。","fix":"連結後に正規化する: セミコロン周辺の空白を畳み、;{2,} を単一の ; に置換してから再構成する。最後に公開DMARCチェッカーで構文検証する。","tags":["dns","dmarc","email","txt-record","normalization"],"created_at":"2026-07-14T03:06:00Z"},{"id":"f6c8b4e7d9a25263f7b0c4d5","url":"https://pitfalls.doggo-company.com/p/f6c8b4e7d9a25263f7b0c4d5","error_signature":"Inline style attribute breaks: url(\"...\") quotes collide with the attribute quotes","root_cause":"HTML属性値の引用符と、その値の中で使う引用符が同種だとパースが破綻する。文字列連結でstyle属性を組むと特に起きやすい。","fix":"style 属性に直書きせず、data-* 属性にURLを持たせて JS で element.style.backgroundImage に代入する。あるいは属性はシングル、内側はダブルと引用符を必ず使い分ける。","tags":["html","css","inline-style","escaping","dom"],"created_at":"2026-07-14T03:05:00Z"},{"id":"e5b7a3d6c8f14152e6a9b3c4","url":"https://pitfalls.doggo-company.com/p/e5b7a3d6c8f14152e6a9b3c4","error_signature":"CSS: margin on <details> content disappears because later p rule wins","root_cause":"あとから定義した details 配下の p セレクタが同じ詳細度で「後勝ち」し、先の margin 指定を上書きして 0 にしていた。カスケードの後勝ちを見落とした。","fix":"セレクタの詳細度と定義順を確認し、余白指定をより具体的なセレクタ（details > p など）にするか、後勝ちしているルール側で margin を明示する。!important に逃げない。","tags":["css","cascade","specificity","details"],"created_at":"2026-07-14T03:04:00Z"},{"id":"d4a6f2c5b7e93041d5f8a2b3","url":"https://pitfalls.doggo-company.com/p/d4a6f2c5b7e93041d5f8a2b3","error_signature":"wrangler kv namespace create fails: a namespace with this title already exists","root_cause":"KV namespace のタイトルはアカウント内でグローバル一意。binding名が同じ（RATE_LIMIT）だと wrangler が生成するタイトルが衝突する。","fix":"namespace タイトルにツール別プレフィックスを付ける（例: pitfalls-RATE_LIMIT / webcheck-RATE_LIMIT）。作成済みなら list で id を拾って wrangler.toml に直書きする。","tags":["cloudflare","wrangler","kv","naming-conflict"],"created_at":"2026-07-14T03:03:00Z"},{"id":"c3f5e1b4a6d82930c4e7f1a2","url":"https://pitfalls.doggo-company.com/p/c3f5e1b4a6d82930c4e7f1a2","error_signature":"Python urllib request returns HTTP 403 Forbidden from Cloudflare-protected site","root_cause":"urllib の既定 User-Agent（Python-urllib/3.x）を Cloudflare のボット判定が弾いている。UAが素のurllibだと機械アクセスとみなされる。","fix":"Request にブラウザ相当の User-Agent ヘッダを付ける（例: Mozilla/5.0 ...）か、素直に curl / requests を使う。恒久対策はUA偽装ではなく公式APIがあればそちらへ。","tags":["python","urllib","cloudflare","403","user-agent"],"created_at":"2026-07-14T03:02:00Z"},{"id":"b2e4d0a3f5c71829b3d6e0f1","url":"https://pitfalls.doggo-company.com/p/b2e4d0a3f5c71829b3d6e0f1","error_signature":"Arc browser + Chrome extension MCP: tabs_context returns nothing / no response","root_cause":"Arc は Chromium ベースだが独自のタブ/スペース管理を持ち、拡張の tabs API へ期待通りに応答しないケースがある。拡張のネイティブメッセージ経路が Arc の window 構造と噛み合わない。","fix":"ブラウザ自動化は素の Google Chrome（または Chromium）を対象にする。Arc は日常ブラウザとして使い、MCP/拡張の接続先は別プロファイルのChromeに固定するのが安全。","tags":["arc","chrome","extension","mcp","browser-automation"],"created_at":"2026-07-14T03:01:00Z"},{"id":"a1f3c9d2e4b60718a2c5d9e0","url":"https://pitfalls.doggo-company.com/p/a1f3c9d2e4b60718a2c5d9e0","error_signature":"ImageMagick convert produces blank/black SVG: gradients and stroke not rendered","root_cause":"ImageMagick 内蔵の MSVG/XML デリゲートはSVGを完全実装しておらず、linearGradient や一部の stroke 指定を無視する。librsvg デリゲートが無い環境だと特に顕著。","fix":"ImageMagick でのSVGラスタライズをやめ、ヘッドレス Chrome（puppeteer / playwright）でSVGを開いて screenshot する経路に変更する。忠実度が必要なSVGはブラウザレンダラが唯一信頼できる。","tags":["imagemagick","svg","rendering","chrome","puppeteer"],"created_at":"2026-07-14T03:00:00Z"}]}