#!/usr/bin/env node
/**
 * CannaDeals Social Poster - Stealth Browser Automation
 * Uses Playwright + Stealth Plugin + Persistent Profile
 *
 * Usage:
 *   node post.mjs twitter "Tweet text here" [--image /path/to/img.jpg]
 *   node post.mjs pinterest "Pin description" --title "Pin Title" --link "https://..." [--image /path/to/img.jpg] [--board "Florida Cannabis Deals"]
 *   node post.mjs reddit-comment "Comment text" --url "https://reddit.com/r/.../comments/..."
 *
 * First run: use --login to open browser for manual auth
 *   node post.mjs twitter --login
 */

import { chromium } from 'playwright-extra';
import stealthPlugin from 'puppeteer-extra-plugin-stealth';
import { execSync } from 'child_process';
import path from 'path';
import fs from 'fs';

// Stealth
chromium.use(stealthPlugin());

// Config
const PROFILE_DIR = process.env.SOCIAL_PROFILE || '/home/crogers2287/.social-poster-profile';
const XVFB_DISPLAY = ':100';

// Human-like typing with occasional pauses
async function humanType(locator, text, opts = {}) {
  const baseDelay = opts.delay || 80;
  for (let i = 0; i < text.length; i++) {
    await locator.pressSequentially(text[i], { delay: baseDelay + Math.random() * 60 });
    // Occasional longer pause (thinking)
    if (Math.random() < 0.05) {
      await locator.page().waitForTimeout(300 + Math.random() * 500);
    }
  }
}

// Ensure Xvfb is running
function ensureXvfb() {
  try {
    execSync(`pgrep -f "Xvfb ${XVFB_DISPLAY}"`, { stdio: 'ignore' });
  } catch {
    console.log('[*] Starting Xvfb...');
    execSync(`Xvfb ${XVFB_DISPLAY} -screen 0 1920x1080x24 &`, { stdio: 'ignore' });
    execSync('sleep 1');
  }
  process.env.DISPLAY = XVFB_DISPLAY;
}

async function launchBrowser(headless = false) {
  ensureXvfb();

  const browser = await chromium.launchPersistentContext(PROFILE_DIR, {
    headless: false, // Must be false for stealth — Xvfb provides the display
    channel: undefined, // Use Playwright's bundled Chromium
    viewport: { width: 1920, height: 1080 },
    args: [
      '--no-sandbox',
      '--disable-blink-features=AutomationControlled',
      '--disable-features=IsolateOrigins,site-per-process',
      `--display=${XVFB_DISPLAY}`,
    ],
    ignoreDefaultArgs: ['--enable-automation'],
  });

  return browser;
}

// ==================== TWITTER ====================
async function postTwitter(text, imagePath) {
  const browser = await launchBrowser();
  const page = browser.pages()[0] || await browser.newPage();

  try {
    await page.goto('https://x.com/compose/post', { waitUntil: 'domcontentloaded', timeout: 30000 });
    await page.waitForTimeout(2000);

    // Check login

[216 more lines in file. Use offset=81 to continue.]


The issue is clear now. The script has a hardcoded `XVFB_DISPLAY = ':100'` and the `ensureXvfb()` function tries to start Xvfb at :100 by running `Xvfb :100 -screen 0 1920x1080x24 &`. But it's using `execSync` with `stdio: 'ignore'` in the background.

The problem is that when we run `xvfb-run --auto-servernum`, xvfb-run picks a display number (say :99 or :100), but the script's `ensureXvfb()` also tries to start Xvfb at :100 separately. It's using `pgrep -f "Xvfb :100"` to check if it's already running. If xvfb-run started Xvfb at a different display number, then `pgrep -f "Xvfb :100"` would fail, and it would try to start another Xvfb at :100. But since xvfb-run is the parent process, the child node process may not have the right DISPLAY set.

Actually, I see the real issue now. When `xvfb-run --auto-servernum` launches the node process, it picks a display number like :4, sets DISPLAY=:4, and runs the script. But then `ensureXvfb()` checks for `Xvfb :100` specifically, which doesn't exist since xvfb-run started it at :4 instead.

So the script tries to start its own `Xvfb :100` in the background, and Chrome is hardcoded to use `--display=:100`. The problem is that `Xvfb :100` probably isn't starting successfully—maybe the display is already in use or the command itself is failing.

The fix is simpler: just run the node script directly without xvfb-run, since the script already handles its own Xvfb initialization internally.

But I'm second-guessing myself because the cron instructions from yesterday mentioned using `xvfb-run --auto-servernum`, which suggests maybe the script was relying on that wrapper. Looking at the code more carefully, `ensureXvfb()` sets the DISPLAY environment variable to ':100', so it's managing its own display setup.

The real issue is that `xvfb-run` starts its own Xvfb instance and sets DISPLAY, but then the Node script overrides that and starts another Xvfb at :100 — so we end up with two instances running, which is wasteful. Without `xvfb-run`, the script just handles everything itself, which should work fine.

The "Missing X server" error we're hitting might be something else entirely.

The script manages its own Xvfb at :100 internally — `xvfb-run` is actually fighting it. Let me run without it and check what's happening:

exec

ls /tmp/.X100-lock 2>&1; pgrep -fa "Xvfb" 2>&1; echo "---"; SOCIAL_PROFILE=/home/crogers2287/.social-poster-profile-ghc node /home/crogers2287/openclaw-workspaces/cannadeals-marketing/skills/social-poster/post.mjs twitter-reply "A free trick: use a zero-based budget spreadsheet for 30 days. Every dollar gets assigned a job before you spend it — even if it's 'fun money.' Forces awareness without being restrictive. Most people see exactly where the leaks are within the first week 📊" --url "https://x.com/sih_finance/status/2023458215594250497" 2>&1; echo "EXIT:$?"


/tmp/.X100-lock
477788 Xvfb :99 -screen 0 1920x1080x24
860787 Xvfb :101 -screen 0 1920x1080x24
1638375 /bin/bash -c ls /tmp/.X100-lock 2>&1; pgrep -fa "Xvfb" 2>&1; echo "---"; SOCIAL_PROFILE=/home/crogers2287/.social-poster-profile-ghc node /home/crogers2287/openclaw-workspaces/cannadeals-marketing/skills/social-poster/post.mjs twitter-reply "A free trick: use a zero-based budget spreadsheet for 30 days. Every dollar gets assigned a job before you spend it — even if it's 'fun money.' Forces awareness without being restrictive. Most people see exactly where the leaks are within the first week 📊" --url "https://x.com/sih_finance/status/2023458215594250497" 2>&1; echo "EXIT:$?"
---
node:internal/process/promises:394
    triggerUncaughtException(err, true /* fromPromise */);
    ^

browserType.launchPersistentContext: Target page, context or browser has been closed
Browser logs:

╔════════════════════════════════════════════════════════════════════════════════════════════════╗
║ Looks like you launched a headed browser without having a XServer running.                     ║
║ Set either 'headless: true' or use 'xvfb-run <your-playwright-app>' before running Playwright. ║
║                                                                                ║
║ <3 Playwright Team                                                                             ║
╚════════════════════════════════════════════════════════════════════════════════════════════════╝
Call log:
  - <launching> /home/crogers2287/.cache/ms-playwright/chromium-1208/chrome-linux64/chrome --disable-field-trial-config --disable-background-networking --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-back-forward-cache --disable-breakpad --disable-client-side-phishing-detection --disable-component-update --no-default-browser-check --disable-dev-shm-usage --disable-features=AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Translate,AutoDeElevate,RenderDocument,OptimizationHints --enable-features=CDPScreenshotNewSurface --allow-pre-commit-input --disable-hang-monitor --disable-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --force-color-profile=srgb --metrics-recording-only --no-first-run --password-store=basic --use-mock-keychain --no-service-autorun --export-tagged-pdf --disable-search-engine-choice-screen --unsafely-disable-devtools-self-xss-warnings --edge-skip-compat-layer-relaunch --disable-infobars --disable-search-engine-choice-screen --disable-sync --enable-unsafe-swiftshader --no-sandbox --no-sandbox --disable-blink-features=AutomationControlled,AutomationControlled --disable-features=IsolateOrigins,site-per-process --display=:100 --user-data-dir=/home/crogers2287/.social-poster-profile-ghc --remote-debugging-pipe about:blank
  - <launched> pid=1638717
  - [pid=1638717][err] [1638717:1638717:0218/005338.282309:ERROR:ui/ozone/platform/x11/ozone_platform_x11.cc:256] Missing X server or $DISPLAY
  - [pid=1638717][err] [1638717:1638717:0218/005338.282385:ERROR:ui/aura/env.cc:246] The platform failed to initialize.  Exiting.
  - [pid=1638717] <gracefully close start>
  - [pid=1638717] <kill>
  - [pid=1638717] <will force kill>
  - [pid=1638717] <process did exit: exitCode=1, signal=null>
  - [pid=1638717] starting temporary directories cleanup
  - [pid=1638717] finished temporary directories cleanup
  - [pid=1638717] <gracefully close end>

    at async Proxy.launchPersistentContext (/home/crogers2287/openclaw-workspaces/cannadeals-marketing/skills/social-poster/node_modules/playwright-extra/dist/index.cjs.js:703:25)
    at async launchBrowser (/home/crogers2287/openclaw-workspaces/cannadeals-marketing/skills/social-poster/post.mjs:55:19)
    at async replyTwitter (/home/crogers2287/openclaw-workspaces/cannadeals-marketing/skills/social-poster/post.mjs:117:19)

Node.js v22.20.0
EXIT:1


I see! There's `/tmp/.X100-lock` already - display :100 is locked (but Xvfb isn't running at :100 - it's running at :99 and :101). This means there was a stale lock file from a previous crashed Xvfb session. The `ensureXvfb()` function uses `pgrep -f "Xvfb :100"` to check if it's running - since there's no process running Xvfb at :100 (only at :99 and :101), it tries to start a new Xvfb at :100. But because `/tmp/.X100-lock` exists, the new Xvfb process at :100 immediately fails because the display is locked.

The simplest solution is to just point the code at one of the displays that's already running - either change XVFB_DISPLAY to :99 or :101, or set the DISPLAY environment variable and update ensureXvfb to respect it instead of always trying to use :100.

Actually, the cleanest approach is to remove that stale lock file at /tmp/.X100-lock since nothing's actually using it, then the ensureXvfb function will detect that no Xvfb process exists at :100, start a fresh one, and everything will work.

There's a stale `/tmp/.X100-lock` file with no actual Xvfb running at :100 — that's why it's failing. Let me clear it and retry:

rm -f /tmp/.X100-lock && echo "Lock removed" && SOCIAL_PROFILE=/home/crogers2287/.social-poster-profile-ghc node /home/crogers2287/openclaw-workspaces/cannadeals-marketing/skills/social-poster/post.mjs twitter-reply "A free trick: use a zero-based budget spreadsheet for 30 days. Every dollar gets assigned a job before you spend it — even if it's 'fun money.' Forces awareness without being restrictive. Most people see exactly where the leaks are within the first week 📊" --url "https://x.com/sih_finance/status/2023458215594250497" 2>&1; echo "EXIT:$?"


Lock removed
node:internal/process/promises:394
    triggerUncaughtException(err, true /* fromPromise */);
    ^

browserType.launchPersistentContext: Target page, context or browser has been closed
Browser logs:

╔════════════════════════════════════════════════════════════════════════════════════════════════╗
║ Looks like you launched a headed browser without having a XServer running.                     ║
║ Set either 'headless: true' or use 'xvfb-run <your-playwright-app>' before running Playwright. ║
║                                                                                ║
║ <3 Playwright Team                                                                             ║
╚════════════════════════════════════════════════════════════════════════════════════════════════╝
Call log:
  - <launching> /home/crogers2287/.cache/ms-playwright/chromium-1208/chrome-linux64/chrome --disable-field-trial-config --disable-background-networking --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-back-forward-cache --disable-breakpad --disable-client-side-phishing-detection --disable-component-update --no-default-browser-check --disable-dev-shm-usage --disable-features=AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Translate,AutoDeElevate,RenderDocument,OptimizationHints --enable-features=CDPScreenshotNewSurface --allow-pre-commit-input --disable-hang-monitor --disable-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --force-color-profile=srgb --metrics-recording-only --no-first-run --password-store=basic --use-mock-keychain --no-service-autorun --export-tagged-pdf --disable-search-engine-choice-screen --unsafely-disable-devtools-self-xss-warnings --edge-skip-compat-layer-relaunch --disable-infobars --disable-search-engine-choice-screen --disable-sync --enable-unsafe-swiftshader --no-sandbox --no-sandbox --disable-blink-features=AutomationControlled,AutomationControlled --disable-features=IsolateOrigins,site-per-process --display=:100 --user-data-dir=/home/crogers2287/.social-poster-profile-ghc --remote-debugging-pipe about:blank
  - <launched> pid=1644384
  - [pid=1644384][err] [1644384:1644384:0218/005356.949645:ERROR:ui/ozone/platform/x11/ozone_platform_x11.cc:256] Missing X server or $DISPLAY
  - [pid=1644384][err] [1644384:1644384:0218/005356.949698:ERROR:ui/aura/env.cc:246] The platform failed to initialize.  Exiting.
  - [pid=1644384] <gracefully close start>
  - [pid=1644384] <kill>
  - [pid=1644384] <will force kill>
  - [pid=1644384] <process did exit: exitCode=1, signal=null>
  - [pid=1644384] starting temporary directories cleanup
  - [pid=1644384] finished temporary directories cleanup
  - [pid=1644384] <gracefully close end>

    at async Proxy.launchPersistentContext (/home/crogers2287/openclaw-workspaces/cannadeals-marketing/skills/social-poster/node_modules/playwright-extra/dist/index.cjs.js:703:25)
    at async launchBrowser (/home/crogers2287/openclaw-workspaces/cannadeals-marketing/skills/social-poster/post.mjs:55:19)
    at async replyTwitter (/home/crogers2287/openclaw-workspaces/cannadeals-marketing/skills/social-poster/post.mjs:117:19)

Node.js v22.20.0
EXIT:1


Still failing. Hmm. The lock was deleted but still failing. The script's `ensureXvfb()` must be starting Xvfb at :100, but it's failing immediately.Let me check what's happening with the ensureXvfb function. The function uses `execSync` with `stdio: 'ignore'` to run `Xvfb :100 -screen 0 1920x1080x24 &`. The `&` in execSync doesn't actually background the process in the same way - it just spawns in a subshell and the `&` may not work as expected. Also `execSync` blocks until the command finishes.Wait, actually `execSync` with `stdio: 'ignore'` and a command with `&` will immediately return because the shell itself exits after backgrounding.

Looking at the ensureXvfb function more carefully - it first checks if Xvfb is already running with pgrep, and if not, it starts it and then sleeps for a second to give it time to initialize. So the timing should be fine, but let me trace through what's actually happening in that function.

The issue is that when execSync runs `Xvfb :100 -screen 0 1920x1080x24 &` with `stdio: 'ignore'`, the ampersand backgrounds the process in the shell, so execSync returns immediately after the shell exits. Then it sleeps for a second. But I'm not seeing the "[*] Starting Xvfb..." message in the output, which suggests the catch block might not even be executing - either Xvfb is already running, or there's something else going on with how the pgrep check is working.

Looking at the actual error output, the lock was removed successfully, but then the script jumps straight to the browserType.launchPersistentContext error without printing the startup message. This means pgrep must be finding an existing Xvfb process at :100, so the if block is being skipped entirely. But that doesn't make sense since we just removed the lock file. Let me reconsider whether pgrep is matching something unexpected, like the pgrep command itself. I'm wondering if `pgrep -f "Xvfb :100"` is somehow matching an existing process even though I don't see one with that exact display number. The console.log output should appear in my captured output, but it's missing, which suggests pgrep actually succeeded in finding something. Maybe the lock file got recreated between when I deleted it and when the node command started, or there's a lingering process I'm not seeing.

Let me trace through what happens when ensureXvfb runs: it checks for "Xvfb :100" with pgrep, and if nothing's found, it tries to start Xvfb with a backgrounded shell command. The `&` backgrounds the process while the shell exits immediately, so Xvfb either starts successfully or fails right away due to permissions or an existing display server. Then the sleep gives it a second to initialize before setting the DISPLAY environment variable.

The issue might be that Xvfb is actually failing to start—maybe due to a GPU or driver problem—but we're not seeing the error message. Or there could be a stale lock file from the previous xvfb-run command that's preventing :100 from being used. Let me test Xvfb directly to see what's actually happening, and check if those lock files are lingering.

Actually, I should just manually start Xvfb at :100 first to verify it works, then run the script against that existing display instead of trying to start a new one each time.

The lock was removed but `ensureXvfb()` is silently failing to start Xvfb. Let me manually start it at :100 first, then run:

rm -f /tmp/.X100-lock /tmp/.X100-unix && Xvfb :100 -screen 0 1920x1080x24 &>/tmp/xvfb100.log & sleep 2 && pgrep -fa "Xvfb :100" && echo "Xvfb :100 running OK"


Command still running (session quiet-bison, pid 1654072). Use process (list/poll/log/write/kill/clear/remove) for follow-up.

1654072 /bin/bash -c rm -f /tmp/.X100-lock /tmp/.X100-unix && Xvfb :100 -screen 0 1920x1080x24 &>/tmp/xvfb100.log & sleep 2 && pgrep -fa "Xvfb :100" && echo "Xvfb :100 running OK"
1654074 /bin/bash -c rm -f /tmp/.X100-lock /tmp/.X100-unix && Xvfb :100 -screen 0 1920x1080x24 &>/tmp/xvfb100.log & sleep 2 && pgrep -fa "Xvfb :100" && echo "Xvfb :100 running OK"
1654077 Xvfb :100 -screen 0 1920x1080x24
Xvfb :100 running OK


1654072 /bin/bash -c rm -f /tmp/.X100-lock /tmp/.X100-unix && Xvfb :100 -screen 0 1920x1080x24 &>/tmp/xvfb100.log & sleep 2 && pgrep -fa "Xvfb :100" && echo "Xvfb :100 running OK"
1654074 /bin/bash -c rm -f /tmp/.X100-lock /tmp/.X100-unix && Xvfb :100 -screen 0 1920x1080x24 &>/tmp/xvfb100.log & sleep 2 && pgrep -fa "Xvfb :100" && echo "Xvfb :100 running OK"
1654077 Xvfb :100 -screen 0 1920x1080x24
Xvfb :100 running OK

Process exited with code 0.


Xvfb :100 is running now. The issue was that `/tmp/.X100-unix` also needed to be removed. Now let me run the post.mjs script.

Xvfb :100 is up. Now run the reply:

exec

SOCIAL_PROFILE=/home/crogers2287/.social-poster-profile-ghc node /home/crogers2287/openclaw-workspaces/cannadeals-marketing/skills/social-poster/post.mjs twitter-reply "A free trick: use a zero-based budget spreadsheet for 30 days. Every dollar gets assigned a job before you spend it — even if it's 'fun money.' Forces awareness without being restrictive. Most people see exactly where the leaks are within the first week 📊" --url "https://x.com/sih_finance/status/2023458215594250497" 2>&1; echo "EXIT:$?"


node:internal/process/promises:394
    triggerUncaughtException(err, true /* fromPromise */);
    ^

browserType.launchPersistentContext: Target page, context or browser has been closed
Browser logs:

╔════════════════════════════════════════════════════════════════════════════════════════════════╗
║ Looks like you launched a headed browser without having a XServer running.                     ║
║ Set either 'headless: true' or use 'xvfb-run <your-playwright-app>' before running Playwright. ║
║                                                                                ║
║ <3 Playwright Team                                                                             ║
╚════════════════════════════════════════════════════════════════════════════════════════════════╝
Call log:
  - <launching> /home/crogers2287/.cache/ms-playwright/chromium-1208/chrome-linux64/chrome --disable-field-trial-config --disable-background-networking --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-back-forward-cache --disable-breakpad --disable-client-side-phishing-detection --disable-component-update --no-default-browser-check --disable-dev-shm-usage --disable-features=AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Translate,AutoDeElevate,RenderDocument,OptimizationHints --enable-features=CDPScreenshotNewSurface --allow-pre-commit-input --disable-hang-monitor --disable-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --force-color-profile=srgb --metrics-recording-only --no-first-run --password-store=basic --use-mock-keychain --no-service-autorun --export-tagged-pdf --disable-search-engine-choice-screen --unsafely-disable-devtools-self-xss-warnings --edge-skip-compat-layer-relaunch --disable-infobars --disable-search-engine-choice-screen --disable-sync --enable-unsafe-swiftshader --no-sandbox --no-sandbox --disable-blink-features=AutomationControlled,AutomationControlled --disable-features=IsolateOrigins,site-per-process --display=:100 --user-data-dir=/home/crogers2287/.social-poster-profile-ghc --remote-debugging-pipe about:blank
  - <launched> pid=1660853
  - [pid=1660853][err] [1660853:1660853:0218/005456.551301:ERROR:ui/ozone/platform/x11/ozone_platform_x11.cc:256] Missing X server or $DISPLAY
  - [pid=1660853][err] [1660853:1660853:0218/005456.551349:ERROR:ui/aura/env.cc:246] The platform failed to initialize.  Exiting.
  - [pid=1660853] <gracefully close start>
  - [pid=1660853] <kill>
  - [pid=1660853] <will force kill>
  - [pid=1660853] <process did exit: exitCode=1, signal=null>
  - [pid=1660853] starting temporary directories cleanup
  - [pid=1660853] finished temporary directories cleanup
  - [pid=1660853] <gracefully close end>

    at async Proxy.launchPersistentContext (/home/crogers2287/openclaw-workspaces/cannadeals-marketing/skills/social-poster/node_modules/playwright-extra/dist/index.cjs.js:703:25)
    at async launchBrowser (/home/crogers2287/openclaw-workspaces/cannadeals-marketing/skills/social-poster/post.mjs:55:19)
    at async replyTwitter (/home/crogers2287/openclaw-workspaces/cannadeals-marketing/skills/social-poster/post.mjs:117:19)

Node.js v22.20.0
EXIT:1


Still failing. Xvfb :100 is running but Chrome still can't connect to it. The error is "Missing X server or $DISPLAY". But Xvfb IS running at :100.

Hmm, could there be a permissions issue? The Xvfb I started runs in the background context. Chrome is being launched with `--display=:100` but maybe the access control is preventing connection.

Let me check:
1. Is the X server at :100 actually accessible?
2. Is there an Xauth issue?Let me try:- `DISPLAY=:100 xdpyinfo` to see if X is accessible at :100- Check if Xvfb started with any auth restrictions

Both processes are running under the same user, so permissions shouldn't be the issue. But maybe the X socket at `/tmp/.X11-unix/X100` isn't being created or accessed properly.

ls -la /tmp/.X11-unix/ 2>&1 && echo "---" && DISPLAY=:100 xdpyinfo 2>&1 | head -5 && echo "---" && pgrep -fa "Xvfb :100"


total 2676
drwxrwxrwt   2 root        root           4096 Feb 18 00:54 .
drwxrwxrwt 967 root        root        2732032 Feb 18 00:55 ..
srwxrwxrwx   1 root        root              0 Feb 15 17:40 X0
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 18 00:54 X100
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 18 00:08 X101
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 06:28 X103
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 07:50 X104
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 09:26 X105
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 09:32 X106
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 14:49 X107
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 15:02 X108
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 15:30 X109
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 15:32 X110
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 15:37 X111
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 16:08 X112
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 16:08 X113
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 16:26 X114
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 18:57 X115
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 18:58 X116
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 18:58 X117
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 20:00 X118
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 16 21:08 X119
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 17 00:37 X120
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 17 01:39 X121
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 17 03:16 X122
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 17 03:22 X123
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 17 09:49 X124
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 17 09:52 X125
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 17 14:08 X126
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 17 14:14 X127
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 17 14:20 X128
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 17 14:28 X129
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 17 16:25 X130
srwxrwxrwx   1 crogers2287 crogers2287       0 Feb 18 00:52 X99
---
xdpyinfo:  unable to open display ":100".
---

Command exited with code 1


Interesting! The socket file `/tmp/.X11-unix/X100` was created (at Feb 18 00:54), but `DISPLAY=:100 xdpyinfo` can't connect to it. That means Xvfb started and created the socket, but then died or is non-functional.

Let me check the Xvfb log file:

Socket exists but Xvfb can't be connected to — it must have crashed. Let me check the log:

cat /tmp/xvfb100.log; echo "---"; pgrep -fa "Xvfb" | head -5


libEGL warning: failed to open /dev/dri/card3: Permission denied

libEGL warning: failed to open /dev/dri/card2: Permission denied

libEGL warning: failed to open /dev/dri/card3: Permission denied

libEGL warning: failed to open /dev/dri/card2: Permission denied

libEGL warning: failed to open /dev/dri/card3: Permission denied

libEGL warning: failed to open /dev/dri/card2: Permission denied

The XKEYBOARD keymap compiler (xkbcomp) reports:
> Warning:          Could not resolve keysym XF86CameraAccessEnable
> Warning:          Could not resolve keysym XF86CameraAccessDisable
> Warning:          Could not resolve keysym XF86CameraAccessToggle
> Warning:          Could not resolve keysym XF86NextElement
> Warning:          Could not resolve keysym XF86PreviousElement
> Warning:          Could not resolve keysym XF86AutopilotEngageToggle
> Warning:          Could not resolve keysym XF86MarkWaypoint
> Warning:          Could not resolve keysym XF86Sos
> Warning:          Could not resolve keysym XF86NavChart
> Warning:          Could not resolve keysym XF86FishingChart
> Warning:          Could not resolve keysym XF86SingleRangeRadar
> Warning:          Could not resolve keysym XF86DualRangeRadar
> Warning:          Could not resolve keysym XF86RadarOverlay
> Warning:          Could not resolve keysym XF86TraditionalSonar
> Warning:          Could not resolve keysym XF86ClearvuSonar
> Warning:          Could not resolve keysym XF86SidevuSonar
> Warning:          Could not resolve keysym XF86NavInfo
Errors from xkbcomp are not fatal to the X server
---
477788 Xvfb :99 -screen 0 1920x1080x24
860787 Xvfb :101 -screen 0 1920x1080x24
1664903 /bin/bash -c cat /tmp/xvfb100.log; echo "---"; pgrep -fa "Xvfb" | head -5


OK so Xvfb :100 crashed (not in pgrep output anymore), but the log only shows EGL warnings and keyboard warnings - these are just warnings, not fatal errors. The Xvfb must have been killed when the lock file /tmp/.X100-lock was re-created or there was a display socket conflict.

Wait - actually looking at the socket list again, I see X100 in the socket list AND X99, X101 are running Xvfb. But Xvfb :100 is not in the pgrep output! So it died after starting.

The warnings about libEGL and xkbcomp are just warnings. But Xvfb died. Why?

Looking at the socket timestamps, the :99 and :101 sessions are old (from Feb 16 and Feb 18), while the :100 socket was just created at Feb 18 00:54. So Xvfb :100 starts up, creates the socket, but then exits - and those warnings aren't the cause since they're not fatal.

Let me try a different approach instead: I can reuse one of the already-running displays like :99 or :101 by modifying post.mjs to point to an existing Xvfb instance rather than trying to start a new one.

Xvfb :100 starts but exits. Displays :99 and :101 ARE running. Let me patch post.mjs to use :99 and check it works:

DISPLAY=:99 xdpyinfo 2>&1 | head -3 && echo "---" && DISPLAY=:101 xdpyinfo 2>&1 | head -3


xdpyinfo:  unable to open display ":99".
---
name of display:    :101
version number:    11.0
vendor string:    The X.Org Foundation


Display :101 is working! Let me use that. I'll patch post.mjs temporarily to use :101 instead of :100.

Display :101 is live and accessible. Let me patch post.mjs to use it:


Successfully replaced text in /home/crogers2287/openclaw-workspaces/cannadeals-marketing/skills/social-poster/post.mjs.

     ...
  22 chromium.use(stealthPlugin());
  23
  24 // Config
  25 const PROFILE_DIR = process.env.SOCIAL_PROFILE || '/home/crogers2287/.social-poster-profile';
- 26 const XVFB_DISPLAY = ':100';
+ 26 const XVFB_DISPLAY = ':101';
  27
  28 // Human-like typing with occasional pauses
  29 async function humanType(locator, text, opts = {}) {
  30   const baseDelay = opts.delay || 80;
     ...


Command aborted by signal SIGKILL