Skip to content

454 bug worker screenshot only captures html without cssjs#457

Open
mizhm wants to merge 2 commits into
mainfrom
454-bug-worker-screenshot-only-captures-html-without-cssjs
Open

454 bug worker screenshot only captures html without cssjs#457
mizhm wants to merge 2 commits into
mainfrom
454-bug-worker-screenshot-only-captures-html-without-cssjs

Conversation

@mizhm

@mizhm mizhm commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added support for a new browser-based screenshot workflow, improving how pages are captured during jobs.
    • Introduced a new browser tool setup that can be initialized and used on demand.
  • Bug Fixes

    • Improved screenshot job handling so failures are reported more clearly.
    • Updated browser startup and shutdown behavior to be more reliable and less dependent on shared state.

@mizhm mizhm linked an issue Jun 25, 2026 that may be closed by this pull request
@mizhm mizhm self-assigned this Jun 25, 2026
@mizhm mizhm requested a review from l1ttps June 25, 2026 01:50
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds an agent-browser skill entry, updates worker runtime and module dependencies, introduces a BrowserSession wrapper for screenshot capture, and rewires worker startup and screenshot jobs to use per-session browser execution instead of shared rod state.

Changes

Agent-browser worker integration

Layer / File(s) Summary
Skill metadata and worker tooling
.agents/skills/agent-browser/SKILL.md, skills-lock.json, worker/Dockerfile, worker/go.mod
Adds the agent-browser skill stub and lockfile entry, removes chromium from the worker runtime package list, and drops rod/stealth dependencies from worker/go.mod.
BrowserSession setup and capture
worker/internal/worker/browser.go
Introduces BrowserSession, ScreenshotOpts, and ScreenshotResult, plus ensureAgentBrowserChrome and CLI-driven screenshot capture that reads and base64-encodes the generated file.
Command retries and path helpers
worker/internal/worker/browser.go
Adds retrying agent-browser command execution, stdout capture, transient-error detection, and binary path resolution.
Worker startup and shutdown wiring
worker/internal/worker/client.go
Stops constructing a shared rod browser, initializes a temporary browser session after tools download, updates the scheduled job runner to pass only toolPath, and removes browser-specific shutdown code.
Per-job screenshot flow
worker/internal/worker/job.go, worker/internal/worker/screenshot.go
Creates and closes a per-job BrowserSession, delegates capture through TakeScreenshotBase64, and returns error payloads for screenshot or JSON marshal failures. TakeScreenshotBase64 now uses fixed screenshot options and the session wrapper instead of the old direct browser path.

Sequence Diagram(s)

sequenceDiagram
  participant processJob
  participant BrowserSession
  participant TakeScreenshotBase64
  participant agentBrowserCLI as "agent-browser CLI"
  processJob->>BrowserSession: NewBrowserSession(sessionID, toolPath)
  processJob->>TakeScreenshotBase64: TakeScreenshotBase64(ctx, browser, rawURL)
  TakeScreenshotBase64->>BrowserSession: TakeScreenshot(ctx, url, opts)
  BrowserSession->>agentBrowserCLI: set, open, screenshot --json -
  agentBrowserCLI-->>BrowserSession: screenshot file path in JSON
  BrowserSession-->>TakeScreenshotBase64: base64 image data
  processJob->>BrowserSession: Close()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 I hopped through browser trails so neat,
New sessions bounced on tiny paws fleet.
With agent-browser bright and clear,
I snapped the scene and held it dear.
Hop-hop, the worker sings complete!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: refactoring worker screenshots to load a real browser so pages render CSS/JS.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 454-bug-worker-screenshot-only-captures-html-without-cssjs

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
worker/Dockerfile (1)

28-36: 🩺 Stability & Availability | 🟠 Major

Add missing system dependencies for Chrome browser runtime

The current Dockerfile installs ca-certificates, curl, and dnsutils but lacks the essential shared libraries required to run the Chrome browser installed by agent-browser on Debian bookworm-slim (e.g., libnss3, libatk1.0-0, libx11-6). Without these, the browser binary will fail to launch, causing screenshots to fail.

Update the apt-get install line in worker/Dockerfile to include the necessary dependencies:

RUN apt-get update && apt-get install -y --no-install-recommends \
    ca-certificates \
    bash \
    libpcap0.8 \
    curl \
    iputils-ping \
    dnsutils \
    libasound2 \
    libatk1.0-0 \
    libatk-bridge2.0-0 \
    libatspi2.0-0 \
    libcairo2 \
    libcups2 \
    libdbus-1-3 \
    libexpat1 \
    libfontconfig1 \
    libgbm1 \
    libglib2.0-0 \
    libnspr4 \
    libnss3 \
    libpango-1.0-0 \
    libpangocairo-1.0-0 \
    libx11-6 \
    libx11-xcb1 \
    libxcb1 \
    libxcomposite1 \
    libxcursor1 \
    libxdamage1 \
    libxext6 \
    libxfixes3 \
    libxi6 \
    libxrandr2 \
    libxrender1 \
    libxss1 \
    libxtst6 \
    && rm -rf /var/lib/apt/lists/*
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@worker/Dockerfile` around lines 28 - 36, The Dockerfile’s base runtime image
is missing shared libraries required by the Chrome browser bundled with
agent-browser, so screenshots and browser launch can fail. Update the apt-get
install block in worker/Dockerfile to include the full set of Chrome runtime
dependencies (for example the libnss3, libatk1.0-0, libx11-6, and related
X11/GTK libraries) alongside the existing packages. Keep the change localized to
the install step so the worker image can run the browser binary successfully.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@worker/internal/worker/browser.go`:
- Around line 100-103: The timeout check in the browser load path is using the
wrong context after b.run(waitCtx, "wait", "--load", "load") fails. Update the
error handling in the browser method that performs the load wait to inspect
waitCtx.Err() instead of ctx.Err(), so load-timeout failures are classified
correctly and still return the timeout loading page %s error when the wait
context is exceeded.
- Around line 56-63: ensureAgentBrowserChrome currently calls b.run with
context.Background() for both the "--version" check and the "install" step,
which can block startup forever if agent-browser hangs. Update
ensureAgentBrowserChrome to create bounded contexts with explicit timeouts for
both commands, pass those contexts into b.run, and make sure the contexts are
canceled after each use so the startup path can fail fast instead of waiting
indefinitely.
- Around line 167-175: Transient retry detection in browser execution is only
inspecting stdout from cmd.Output(), so stderr-only transient failures are
missed. Update the retry block in the browser command execution path to combine
stdout with stderr from the returned exec.ExitError before calling
isTransientError, keeping the existing retry/sleep logic in place. Use the
cmd.Output() call and the isTransientError check as the key points to adjust.

In `@worker/internal/worker/client.go`:
- Around line 165-173: The browser startup path in NewBrowserSession/initSession
is not cancellable because ensureAgentBrowserChrome() and Close() use
context.Background(), so shutdown can be delayed while stateMu is held. Update
the initSession flow to thread sessionCtx (or another ctx derived from the
worker ctx) through ensureAgentBrowserChrome and Close, or move the blocking
setup/cleanup outside the locked section so cancellation can interrupt it. Use
the NewBrowserSession, ensureAgentBrowserChrome, and Close call sites in the
worker startup path to make the change.

In `@worker/internal/worker/job.go`:
- Around line 51-53: The screenshot job flow in the worker currently ignores
BrowserSession cleanup failures, which can hide leaks across repeated jobs.
Update the per-job browser handling around NewBrowserSession and
TakeScreenshotBase64 to capture and log any error returned by jobBrowser.Close()
at minimum, and if the browser session supports a context-aware or bounded close
path, use that instead so cleanup failures are visible and handled.
- Around line 51-64: The screenshot result is storing the raw input URL instead
of the normalized URL used for capture. In the job handling flow in job.go,
normalize the URL once with the same logic used by
TakeScreenshotBase64/formatURL, then pass that normalized value into both the
screenshot call and resultData.URL so the payload reflects the actual captured
URL.

In `@worker/internal/worker/screenshot.go`:
- Around line 51-60: The screenshot capture path in TakeScreenshot is swallowing
failures from browser.TakeScreenshot, which lets the caller continue as if the
capture succeeded. Update TakeScreenshot to return the error immediately after
logging it, and make sure the timeout-specific and general log messages stay in
the error path; this will allow job.go to receive the failure and send the
intended error payload instead of an empty screenshot.

---

Outside diff comments:
In `@worker/Dockerfile`:
- Around line 28-36: The Dockerfile’s base runtime image is missing shared
libraries required by the Chrome browser bundled with agent-browser, so
screenshots and browser launch can fail. Update the apt-get install block in
worker/Dockerfile to include the full set of Chrome runtime dependencies (for
example the libnss3, libatk1.0-0, libx11-6, and related X11/GTK libraries)
alongside the existing packages. Keep the change localized to the install step
so the worker image can run the browser binary successfully.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6e94f3cd-9615-4dbe-9b2d-872522965f8c

📥 Commits

Reviewing files that changed from the base of the PR and between 79fe5ca and d852a6d.

⛔ Files ignored due to path filters (5)
  • core-api/public/archived/linux_amd64/agent-browser-v0.29.zip is excluded by !**/*.zip
  • core-api/public/archived/linux_arm64/agent-browser-v0.29.zip is excluded by !**/*.zip
  • core-api/public/archived/macos_amd64/agent-browser-v0.29.zip is excluded by !**/*.zip
  • core-api/public/archived/windows_amd64/agent-browser-v0.29.zip is excluded by !**/*.zip
  • worker/go.sum is excluded by !**/*.sum
📒 Files selected for processing (8)
  • .agents/skills/agent-browser/SKILL.md
  • skills-lock.json
  • worker/Dockerfile
  • worker/go.mod
  • worker/internal/worker/browser.go
  • worker/internal/worker/client.go
  • worker/internal/worker/job.go
  • worker/internal/worker/screenshot.go
💤 Files with no reviewable changes (1)
  • worker/go.mod

Comment on lines +56 to +63
if err := b.run(context.Background(), "--version"); err != nil {
return fmt.Errorf("agent-browser binary not found or not working: %w", err)
}

// Install Chrome
browserLog.Info("Installing Chrome via agent-browser...")
if err := b.run(context.Background(), "install"); err != nil {
return fmt.Errorf("agent-browser install failed: %w", err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add bounded timeouts for setup commands.

ensureAgentBrowserChrome runs external commands with context.Background(), so a stuck agent-browser --version/install can block startup indefinitely. Use explicit timeouts for both calls.

Suggested fix
 func (b *BrowserSession) ensureAgentBrowserChrome() error {
 	browserLog.Info("Checking agent-browser Chrome installation...")
 
-	// Try to run agent-browser to check if it works
-	if err := b.run(context.Background(), "--version"); err != nil {
+	checkCtx, checkCancel := context.WithTimeout(context.Background(), 15*time.Second)
+	defer checkCancel()
+	if err := b.run(checkCtx, "--version"); err != nil {
 		return fmt.Errorf("agent-browser binary not found or not working: %w", err)
 	}
 
 	// Install Chrome
 	browserLog.Info("Installing Chrome via agent-browser...")
-	if err := b.run(context.Background(), "install"); err != nil {
+	installCtx, installCancel := context.WithTimeout(context.Background(), 5*time.Minute)
+	defer installCancel()
+	if err := b.run(installCtx, "install"); err != nil {
 		return fmt.Errorf("agent-browser install failed: %w", err)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if err := b.run(context.Background(), "--version"); err != nil {
return fmt.Errorf("agent-browser binary not found or not working: %w", err)
}
// Install Chrome
browserLog.Info("Installing Chrome via agent-browser...")
if err := b.run(context.Background(), "install"); err != nil {
return fmt.Errorf("agent-browser install failed: %w", err)
checkCtx, checkCancel := context.WithTimeout(context.Background(), 15*time.Second)
defer checkCancel()
if err := b.run(checkCtx, "--version"); err != nil {
return fmt.Errorf("agent-browser binary not found or not working: %w", err)
}
// Install Chrome
browserLog.Info("Installing Chrome via agent-browser...")
installCtx, installCancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer installCancel()
if err := b.run(installCtx, "install"); err != nil {
return fmt.Errorf("agent-browser install failed: %w", err)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@worker/internal/worker/browser.go` around lines 56 - 63,
ensureAgentBrowserChrome currently calls b.run with context.Background() for
both the "--version" check and the "install" step, which can block startup
forever if agent-browser hangs. Update ensureAgentBrowserChrome to create
bounded contexts with explicit timeouts for both commands, pass those contexts
into b.run, and make sure the contexts are canceled after each use so the
startup path can fail fast instead of waiting indefinitely.

Comment on lines +100 to +103
if err := b.run(waitCtx, "wait", "--load", "load"); err != nil {
if ctx.Err() == context.DeadlineExceeded {
return "", fmt.Errorf("timeout loading page %s", url)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Timeout check uses the wrong context.

After b.run(waitCtx, ...) fails, this checks ctx.Err() instead of waitCtx.Err(), so load-timeout errors are often misclassified.

Suggested fix
-	if err := b.run(waitCtx, "wait", "--load", "load"); err != nil {
-		if ctx.Err() == context.DeadlineExceeded {
+	if err := b.run(waitCtx, "wait", "--load", "load"); err != nil {
+		if waitCtx.Err() == context.DeadlineExceeded {
 			return "", fmt.Errorf("timeout loading page %s", url)
 		}
 		return "", fmt.Errorf("wait load: %w", err)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if err := b.run(waitCtx, "wait", "--load", "load"); err != nil {
if ctx.Err() == context.DeadlineExceeded {
return "", fmt.Errorf("timeout loading page %s", url)
}
if err := b.run(waitCtx, "wait", "--load", "load"); err != nil {
if waitCtx.Err() == context.DeadlineExceeded {
return "", fmt.Errorf("timeout loading page %s", url)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@worker/internal/worker/browser.go` around lines 100 - 103, The timeout check
in the browser load path is using the wrong context after b.run(waitCtx, "wait",
"--load", "load") fails. Update the error handling in the browser method that
performs the load wait to inspect waitCtx.Err() instead of ctx.Err(), so
load-timeout failures are classified correctly and still return the timeout
loading page %s error when the wait context is exceeded.

Comment on lines +167 to +175
for attempt := range maxRetries {
cmd := exec.CommandContext(ctx, binaryPath, fullArgs...)
output, err := cmd.Output()
if err == nil {
return string(output), nil
}
out := string(output)
if attempt < maxRetries-1 && isTransientError(out) {
time.Sleep(time.Duration(attempt+1) * 500 * time.Millisecond)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major

Transient retry logic ignores stderr output

cmd.Output() returns only stdout to the output variable; stderr is attached to the returned error (as *exec.ExitError.Stderr). Consequently, the transient check isTransientError(out) misses errors sent to stderr, such as "Resource temporarily unavailable", preventing valid retries.

Combine stdout and stderr before checking for transient errors:

Proposed Fix
output, err := cmd.Output()
if err == nil {
return string(output), nil
}
out := string(output)
if exitErr, ok := err.(*exec.ExitError); ok && len(exitErr.Stderr) > 0 {
out = out + string(exitErr.Stderr)
}
if attempt < maxRetries-1 && isTransientError(out) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@worker/internal/worker/browser.go` around lines 167 - 175, Transient retry
detection in browser execution is only inspecting stdout from cmd.Output(), so
stderr-only transient failures are missed. Update the retry block in the browser
command execution path to combine stdout with stderr from the returned
exec.ExitError before calling isTransientError, keeping the existing retry/sleep
logic in place. Use the cmd.Output() call and the isTransientError check as the
key points to adjust.

Comment on lines +165 to +173
// Initialize browser after tools are downloaded
initSession := NewBrowserSession("init", toolPath)
if err := initSession.ensureAgentBrowserChrome(); err != nil {
sysLog.ErrorE("Failed to setup agent-browser", err)
_ = initSession.Close()
stateMu.Unlock()
continue
}
_ = initSession.Close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make browser setup cancellable during shutdown.

ensureAgentBrowserChrome() and Close() run through context.Background(), so this new startup path can keep running after ctx is canceled and delay shutdown/reconnect while stateMu is held. Thread sessionCtx through setup/close, or move the blocking setup outside the locked section with cancellation support.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@worker/internal/worker/client.go` around lines 165 - 173, The browser startup
path in NewBrowserSession/initSession is not cancellable because
ensureAgentBrowserChrome() and Close() use context.Background(), so shutdown can
be delayed while stateMu is held. Update the initSession flow to thread
sessionCtx (or another ctx derived from the worker ctx) through
ensureAgentBrowserChrome and Close, or move the blocking setup/cleanup outside
the locked section so cancellation can interrupt it. Use the NewBrowserSession,
ensureAgentBrowserChrome, and Close call sites in the worker startup path to
make the change.

Comment on lines +51 to +53
jobBrowser := NewBrowserSession("screenshot-"+job.Id, toolPath)
base64Image, err := TakeScreenshotBase64(ctx, jobBrowser, url)
_ = jobBrowser.Close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not discard per-job browser close failures.

With one BrowserSession per screenshot job, ignoring Close() failures hides cleanup leaks across repeated jobs. Log the close error at minimum, and prefer a bounded/context-aware close path if available.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@worker/internal/worker/job.go` around lines 51 - 53, The screenshot job flow
in the worker currently ignores BrowserSession cleanup failures, which can hide
leaks across repeated jobs. Update the per-job browser handling around
NewBrowserSession and TakeScreenshotBase64 to capture and log any error returned
by jobBrowser.Close() at minimum, and if the browser session supports a
context-aware or bounded close path, use that instead so cleanup failures are
visible and handled.

Comment on lines +51 to +64
jobBrowser := NewBrowserSession("screenshot-"+job.Id, toolPath)
base64Image, err := TakeScreenshotBase64(ctx, jobBrowser, url)
_ = jobBrowser.Close()
if err != nil {
jobLogGlobal.Warning("[%s] Screenshot capture failed: %v", job.Id, err)
}
resultData := struct {
Screenshot string `json:"screenshot"`
URL string `json:"url"`
}{
Screenshot: base64Image,
URL: formatURL(url),
}

if jsonBytes, err := json.Marshal(resultData); err != nil {
jobLogGlobal.ErrorE(fmt.Sprintf("[%s] JSON marshal failed", job.Id), err)
payload = oasm.NewErrorResult(fmt.Sprintf("JSON error: %v", err))
payload = oasm.NewErrorResult(fmt.Sprintf("Screenshot failed: %v", err))
} else {
jsonStr := string(jsonBytes)
payload = &jobs_registry.DataPayloadResult{
Error: false,
Raw: &jsonStr,
resultData := struct {
Screenshot string `json:"screenshot"`
URL string `json:"url"`
}{
Screenshot: base64Image,
URL: url,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Return the URL that was actually captured.

TakeScreenshotBase64 normalizes via formatURL, but the result payload stores the raw command input. Normalize once and reuse that value for both capture and resultData.URL.

Proposed fix
 		url := strings.TrimSpace(after)
+		capturedURL := formatURL(url)
 		jobLogGlobal.Debug("[%s] Capturing screenshot: %s", job.Id, url)
 
 		jobBrowser := NewBrowserSession("screenshot-"+job.Id, toolPath)
-		base64Image, err := TakeScreenshotBase64(ctx, jobBrowser, url)
+		base64Image, err := TakeScreenshotBase64(ctx, jobBrowser, capturedURL)
 		_ = jobBrowser.Close()
@@
 			}{
 				Screenshot: base64Image,
-				URL:        url,
+				URL:        capturedURL,
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
jobBrowser := NewBrowserSession("screenshot-"+job.Id, toolPath)
base64Image, err := TakeScreenshotBase64(ctx, jobBrowser, url)
_ = jobBrowser.Close()
if err != nil {
jobLogGlobal.Warning("[%s] Screenshot capture failed: %v", job.Id, err)
}
resultData := struct {
Screenshot string `json:"screenshot"`
URL string `json:"url"`
}{
Screenshot: base64Image,
URL: formatURL(url),
}
if jsonBytes, err := json.Marshal(resultData); err != nil {
jobLogGlobal.ErrorE(fmt.Sprintf("[%s] JSON marshal failed", job.Id), err)
payload = oasm.NewErrorResult(fmt.Sprintf("JSON error: %v", err))
payload = oasm.NewErrorResult(fmt.Sprintf("Screenshot failed: %v", err))
} else {
jsonStr := string(jsonBytes)
payload = &jobs_registry.DataPayloadResult{
Error: false,
Raw: &jsonStr,
resultData := struct {
Screenshot string `json:"screenshot"`
URL string `json:"url"`
}{
Screenshot: base64Image,
URL: url,
}
url := strings.TrimSpace(after)
capturedURL := formatURL(url)
jobLogGlobal.Debug("[%s] Capturing screenshot: %s", job.Id, url)
jobBrowser := NewBrowserSession("screenshot-"+job.Id, toolPath)
base64Image, err := TakeScreenshotBase64(ctx, jobBrowser, capturedURL)
_ = jobBrowser.Close()
if err != nil {
jobLogGlobal.Warning("[%s] Screenshot capture failed: %v", job.Id, err)
payload = oasm.NewErrorResult(fmt.Sprintf("Screenshot failed: %v", err))
} else {
resultData := struct {
Screenshot string `json:"screenshot"`
URL string `json:"url"`
}{
Screenshot: base64Image,
URL: capturedURL,
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@worker/internal/worker/job.go` around lines 51 - 64, The screenshot result is
storing the raw input URL instead of the normalized URL used for capture. In the
job handling flow in job.go, normalize the URL once with the same logic used by
TakeScreenshotBase64/formatURL, then pass that normalized value into both the
screenshot call and resultData.URL so the payload reflects the actual captured
URL.

Comment on lines +51 to +60
base64Image, err := browser.TakeScreenshot(ctx, url, opts)
if err != nil {
if strings.Contains(err.Error(), "timeout") {
return "", fmt.Errorf("timeout loading page %s", url)
screenshotLog.Error("timeout loading page %s", url)
}
return "", fmt.Errorf("failed to load page %s: %w", url, err)
}

quality := 80
imgBytes, err := page.Timeout(10*time.Second).Screenshot(true, &proto.PageCaptureScreenshot{
Format: proto.PageCaptureScreenshotFormatJpeg,
Quality: &quality,
Clip: &proto.PageViewport{
X: 0, Y: 0, Width: 1920, Height: 1080, Scale: 1,
},
})
if err != nil {
return "", fmt.Errorf("failed to take screenshot: %w", err)
screenshotLog.Error("failed to load page %s", url)
}

screenshotLog.Debug("Screenshot captured successfully: %s", url)
return base64.StdEncoding.EncodeToString(imgBytes), nil
return base64Image, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Propagate screenshot capture failures.

browser.TakeScreenshot errors are logged but then discarded, so job.go never sends the intended error payload and may report success with an empty screenshot.

Proposed fix
 	base64Image, err := browser.TakeScreenshot(ctx, url, opts)
 	if err != nil {
 		if strings.Contains(err.Error(), "timeout") {
 			screenshotLog.Error("timeout loading page %s", url)
 		}
 		screenshotLog.Error("failed to load page %s", url)
+		return "", err
 	}
 
 	screenshotLog.Debug("Screenshot captured successfully: %s", url)
 	return base64Image, nil
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
base64Image, err := browser.TakeScreenshot(ctx, url, opts)
if err != nil {
if strings.Contains(err.Error(), "timeout") {
return "", fmt.Errorf("timeout loading page %s", url)
screenshotLog.Error("timeout loading page %s", url)
}
return "", fmt.Errorf("failed to load page %s: %w", url, err)
}
quality := 80
imgBytes, err := page.Timeout(10*time.Second).Screenshot(true, &proto.PageCaptureScreenshot{
Format: proto.PageCaptureScreenshotFormatJpeg,
Quality: &quality,
Clip: &proto.PageViewport{
X: 0, Y: 0, Width: 1920, Height: 1080, Scale: 1,
},
})
if err != nil {
return "", fmt.Errorf("failed to take screenshot: %w", err)
screenshotLog.Error("failed to load page %s", url)
}
screenshotLog.Debug("Screenshot captured successfully: %s", url)
return base64.StdEncoding.EncodeToString(imgBytes), nil
return base64Image, nil
base64Image, err := browser.TakeScreenshot(ctx, url, opts)
if err != nil {
if strings.Contains(err.Error(), "timeout") {
screenshotLog.Error("timeout loading page %s", url)
}
screenshotLog.Error("failed to load page %s", url)
return "", err
}
screenshotLog.Debug("Screenshot captured successfully: %s", url)
return base64Image, nil
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@worker/internal/worker/screenshot.go` around lines 51 - 60, The screenshot
capture path in TakeScreenshot is swallowing failures from
browser.TakeScreenshot, which lets the caller continue as if the capture
succeeded. Update TakeScreenshot to return the error immediately after logging
it, and make sure the timeout-specific and general log messages stay in the
error path; this will allow job.go to receive the failure and send the intended
error payload instead of an empty screenshot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Worker screenshot only captures HTML without CSS/JS

1 participant