Skip to content

Add Claude Code AI agent integration plugins for ArchiveBox#7

Merged
pirate merged 25 commits intomainfrom
claude/create-new-plugins-p19P7
Mar 15, 2026
Merged

Add Claude Code AI agent integration plugins for ArchiveBox#7
pirate merged 25 commits intomainfrom
claude/create-new-plugins-p19P7

Conversation

@pirate
Copy link
Copy Markdown
Member

@pirate pirate commented Mar 15, 2026

Summary

This PR introduces three new ArchiveBox plugins that integrate Claude Code AI agents for intelligent content processing:

  1. claudecode - Base plugin that installs the Claude Code CLI binary and provides shared utilities
  2. claudecodeextract - Snapshot-level plugin that uses Claude to extract/transform content from existing extractor outputs
  3. claudecodecleanup - Snapshot-level plugin that uses Claude to identify and remove duplicate/redundant outputs

Key Changes

claudecode (Base Plugin)

  • on_Crawl__35_claudecode_install.bg.py: Crawl-level hook that emits a Binary JSONL record requesting installation of @anthropic-ai/claude-code npm package
  • claudecode_utils.py: Shared utility module providing:
    • Environment variable helpers (get_env, get_env_bool, get_env_int)
    • Metadata extraction functions for crawl and snapshot directories
    • build_system_prompt(): Generates context-aware system prompts describing the ArchiveBox directory structure and available extractor outputs
    • run_claude_code(): Wrapper for spawning Claude Code CLI with configurable prompts, timeouts, models, and tool restrictions
    • emit_archive_result(): Helper for emitting ArchiveBox JSONL result records
  • config.json: Configuration schema with settings for API key, model selection, timeout, and max turns
  • Comprehensive test suite validating hook execution, config validity, and utility functions

claudecodeextract (Extraction Plugin)

  • on_Snapshot__58_claudecodeextract.py: Snapshot-level hook (priority 58) that:
    • Reads all existing extractor outputs (readability, mercury, defuddle, etc.)
    • Runs Claude Code with read-only access to generate derived content (default: Markdown representation)
    • Saves AI-generated output to the plugin's output directory
    • Respects CLAUDECODEEXTRACT_ENABLED flag and requires ANTHROPIC_API_KEY
  • config.json: Declares dependency on claudecode plugin, configurable extraction prompt and model
  • Test suite covering disabled state, missing API key, and missing binary scenarios

claudecodecleanup (Cleanup Plugin)

  • on_Snapshot__99_claudecodecleanup.py: Snapshot-level hook (priority 99, runs at end of pipeline) that:
    • Analyzes all extractor outputs to identify duplicates and redundant files
    • Grants Claude Code read/write/delete access to intelligently remove inferior versions
    • Generates a cleanup report documenting what was removed and why
    • Respects CLAUDECODECLEANUP_ENABLED flag and requires ANTHROPIC_API_KEY
  • config.json: Declares dependency on claudecode plugin, higher default max_turns (15) for thorough analysis
  • Test suite covering disabled state, missing API key, and missing binary scenarios

Templates and UI

  • Icon, card, and full-page HTML templates for all three plugins for ArchiveBox UI integration

Notable Implementation Details

  • Tool Restrictions: Claude Code is invoked with restricted tool access:

    • Extract plugin: Read, Write, and safe Bash commands (cat, ls, find, head, tail, wc)
    • Cleanup plugin: Additionally allows rm, rmdir, du, diff for safe file deletion
  • Configuration Inheritance: Child plugins (extract, cleanup) can override base claudecode settings (timeout, model, max_turns) via environment variables with fallback to base plugin defaults

  • System Prompt Context: The build_system_prompt() function dynamically includes:

    • Available extractor output directories and their files
    • Crawl and snapshot metadata
    • Custom instructions for the specific task
  • Error Handling: All hooks gracefully handle missing API keys, missing binaries, and disabled states with appropriate JSONL result records

  • Comprehensive Testing: 253+ lines of tests covering hook existence, config validity, JSONL output format, environment variable handling, and error scenarios

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6


Open with Devin

Summary by cubic

Adds claudechrome for agentic page interaction via Chrome CDP and Anthropic computer-use, plus claudecode, claudecodeextract, and claudecodecleanup powered by @anthropic-ai/claude-code. Adds a base plugin for shared Python/JS helpers, standardizes JSONL output, tightens library permissions, and hardens CI with reliable Chromium and Anthropic prerequisites.

  • New Features

    • claudechrome: optional install/config of the Claude for Chrome extension, but the snapshot hook now drives pages directly via Anthropic Messages (computer_20250124) using curl + puppeteer-core; runs at priority 47; loops screenshot→send→execute (click/type/scroll/key); moves downloads into the plugin output dir; saves a conversation log; adds CLAUDECHROME_MAX_ACTIONS; fixes CHROME_SESSION_DIR; templates hardened (iframe sandbox).
    • claudecode suite:
      • claudecode: crawl install hook renamed to .finite.bg, installs the claude CLI (@anthropic-ai/claude-code), imports helpers from base/utils, emits Binary JSONL, respects CLAUDECODE_ENABLED and CLAUDECODE_BINARY (uses basename if absolute).
      • claudecodeextract (priority 58): reads prior outputs and writes derived content (default content.md) to its dir; scopes reads to SNAP_DIR and writes to its output dir; logs session.json; excludes response.txt/session.json from counts.
      • claudecodecleanup (priority 92): dedupes within SNAP_DIR, writes cleanup_report.txt, broadens allowed tools to Read, Write, Edit, Bash, Glob, Grep; tests verify real deletions and stricter error messages (include stderr).
    • base plugin: shared helpers in base/utils.py and base/utils.js; refactors hooks/tests to use them; standardizes JSONL output; fixes stdlib ssl shadowing via sys.path.append; adds icons; adds uv.lock for reproducible deps.
    • Security/CI/Chrome: enforce_lib_permissions() makes ~/.config/abx/lib/ read‑exec only; findChromium() now searches LIB_DIR; CI fixtures ensure Anthropic prerequisites, auto‑disable Chrome sandbox as root, and resolve LIB_DIR before HOME.
  • Migration

    • Opt-in: set CLAUDECHROME_ENABLED=true with ANTHROPIC_API_KEY (requires chrome); set CLAUDECODE_ENABLED=true before enabling CLAUDECODEEXTRACT_ENABLED/CLAUDECODECLEANUP_ENABLED.
    • Ensure the claude CLI is available (installed via npm or override with CLAUDECODE_BINARY).
    • Optional tuning via CLAUDECHROME_* (incl. CLAUDECHROME_MAX_ACTIONS), CLAUDECODE_*, CLAUDECODEEXTRACT_*, and CLAUDECODECLEANUP_* env vars.

Written for commit a2b0a17. Summary will update on new commits.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional findings.

Open in Devin Review

cubic-dev-ai[bot]

This comment was marked as resolved.

cubic-dev-ai[bot]

This comment was marked as resolved.

cubic-dev-ai[bot]

This comment was marked as resolved.

cubic-dev-ai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

cubic-dev-ai[bot]

This comment was marked as resolved.

cubic-dev-ai[bot]

This comment was marked as resolved.

cubic-dev-ai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 11 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="abx_plugins/plugins/claudechrome/on_Crawl__84_claudechrome_install.bg.js">

<violation number="1" location="abx_plugins/plugins/claudechrome/on_Crawl__84_claudechrome_install.bg.js:33">
P2: Return a non-zero exit code when the extension install returns null; otherwise crawl setup can report success even though Claude for Chrome was never installed.</violation>
</file>

<file name="abx_plugins/plugins/claudechrome/on_Snapshot__47_claudechrome.js">

<violation number="1" location="abx_plugins/plugins/claudechrome/on_Snapshot__47_claudechrome.js:202">
P1: `chrome.sidePanel.open()` is called with a DevTools target id and without a user gesture, so the side panel will not open reliably.</violation>
</file>

<file name="abx_plugins/plugins/claudechrome/templates/full.html">

<violation number="1" location="abx_plugins/plugins/claudechrome/templates/full.html:5">
P2: Remove top-level navigation from this iframe sandbox before previewing untrusted downloaded HTML.</violation>
</file>

<file name="abx_plugins/plugins/claudechrome/tests/test_claudechrome.py">

<violation number="1" location="abx_plugins/plugins/claudechrome/tests/test_claudechrome.py:171">
P1: Custom agent: **Test quality checker**

Replace this placeholder with a real end-to-end test or remove it; a `pass` test for the claimed full pipeline violates the test-quality rule.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread abx_plugins/plugins/claudechrome/on_Snapshot__47_claudechrome.js Outdated
Comment thread abx_plugins/plugins/claudechrome/tests/test_claudechrome.py Outdated

// Check if enabled
if (!getEnvBool('CLAUDECHROME_ENABLED', false)) {
process.exit(0);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 15, 2026

Choose a reason for hiding this comment

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

P2: Return a non-zero exit code when the extension install returns null; otherwise crawl setup can report success even though Claude for Chrome was never installed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abx_plugins/plugins/claudechrome/on_Crawl__84_claudechrome_install.bg.js, line 33:

<comment>Return a non-zero exit code when the extension install returns null; otherwise crawl setup can report success even though Claude for Chrome was never installed.</comment>

<file context>
@@ -0,0 +1,70 @@
+
+// Check if enabled
+if (!getEnvBool('CLAUDECHROME_ENABLED', false)) {
+    process.exit(0);
+}
+
</file context>
Fix with Cubic

<iframe class="full-page-iframe"
src="{{ output_path }}"
name="preview"
sandbox="allow-top-navigation-by-user-activation">
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 15, 2026

Choose a reason for hiding this comment

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

P2: Remove top-level navigation from this iframe sandbox before previewing untrusted downloaded HTML.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abx_plugins/plugins/claudechrome/templates/full.html, line 5:

<comment>Remove top-level navigation from this iframe sandbox before previewing untrusted downloaded HTML.</comment>

<file context>
@@ -0,0 +1,6 @@
+<iframe class="full-page-iframe"
+        src="{{ output_path }}"
+        name="preview"
+        sandbox="allow-top-navigation-by-user-activation">
+</iframe>
</file context>
Fix with Cubic

cubic-dev-ai[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 20 additional findings in Devin Review.

Open in Devin Review

Comment on lines +128 to +137
if (!extPage || !extPage.url().startsWith(`chrome-extension://${extensionId}`)) {
console.warn('[!] Could not access extension context for config injection');
// Still mark as configured so we don't retry
fs.writeFileSync(CONFIG_MARKER, JSON.stringify({
timestamp: new Date().toISOString(),
extensionId,
configured: false,
note: 'Could not access extension context - user may need to log in manually',
}, null, 2));
return { success: true, skipped: true };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Browser page leaked when extension context is unreachable

In the config hook, when no existing extension page is found, a new page is created at line 107 via browser.newPage(). If none of the extension page URLs are successfully navigated (lines 110-121), the check at line 128 fails and the function returns at line 137 without closing the newly created page. The finally block at line 184 only calls browser.disconnect(), which disconnects Puppeteer but does not close browser tabs. This leaves an orphaned blank tab open in the persistent Chrome session, which wastes memory and could interfere with hooks that iterate over or count open browser pages.

Suggested change
if (!extPage || !extPage.url().startsWith(`chrome-extension://${extensionId}`)) {
console.warn('[!] Could not access extension context for config injection');
// Still mark as configured so we don't retry
fs.writeFileSync(CONFIG_MARKER, JSON.stringify({
timestamp: new Date().toISOString(),
extensionId,
configured: false,
note: 'Could not access extension context - user may need to log in manually',
}, null, 2));
return { success: true, skipped: true };
if (!extPage || !extPage.url().startsWith(`chrome-extension://${extensionId}`)) {
console.warn('[!] Could not access extension context for config injection');
// Close the page we opened if it's not an existing extension page
try { await extPage.close(); } catch (e) {}
// Still mark as configured so we don't retry
fs.writeFileSync(CONFIG_MARKER, JSON.stringify({
timestamp: new Date().toISOString(),
extensionId,
configured: false,
note: 'Could not access extension context - user may need to log in manually',
}, null, 2));
return { success: true, skipped: true };
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

cubic-dev-ai[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 22 additional findings in Devin Review.

Open in Devin Review

Comment on lines +517 to +539
messages.push({ role: 'assistant', content: assistantContent });
messages.push({
role: 'user',
content: [
{
type: 'tool_result',
tool_use_id: block.id,
content: [
{
type: 'image',
source: {
type: 'base64',
media_type: 'image/png',
data: screenshot,
},
},
],
},
],
});

// Only process the first tool_use per response
break;
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot Mar 15, 2026

Choose a reason for hiding this comment

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

🔴 Missing tool_results for multi-tool_use responses breaks API on next call

When Claude's response contains multiple tool_use blocks, line 517 pushes the full assistantContent (including all tool_use blocks) into the messages array, but lines 518-536 only append a single tool_result for the first tool_use block (then break at line 539 skips the rest). On the next iteration, callAnthropicAPI sends these messages to the Anthropic API, which requires a matching tool_result for every tool_use in the preceding assistant message. The API will reject the request with an invalid_request_error ("Missing tool_result for tool_use_id ..."), causing the agentic loop to break prematurely at line 477. While the computer_20250124 tool typically returns one action per turn, the API contract doesn't guarantee this, and the code would silently fail if multiple tool_use blocks are returned.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

cubic-dev-ai[bot]

This comment was marked as resolved.

cubic-dev-ai[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 22 additional findings in Devin Review.

Open in Devin Review

Comment on lines +555 to +558
// If stop_reason is end_turn with no tool use, we're done
if (response.stop_reason === 'end_turn' && !hasToolUse) {
break;
}
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot Mar 15, 2026

Choose a reason for hiding this comment

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

🟡 Dead code: second !hasToolUse break is unreachable

At lines 550-552, the loop breaks if !hasToolUse. The subsequent check at lines 556-558 (response.stop_reason === 'end_turn' && !hasToolUse) is unreachable dead code — if !hasToolUse were true, execution would have already exited via the break at line 552. This suggests the developer may have intended different logic for the first check (e.g., only logging without breaking, or checking a different condition), which could mean the loop continues iterating when it shouldn't, or breaks too early depending on original intent.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 4 new potential issues.

View 30 additional findings in Devin Review.

Open in Devin Review

Comment on lines +34 to +42
const {
getEnv,
getEnvBool,
getEnvInt,
parseArgs,
readCdpUrl,
connectToPage,
waitForPageLoaded,
} = require('../chrome/chrome_utils.js');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 claudechrome imports parseArgs from both base/utils.js and chrome_utils.js, causing duplicate import

In claudechrome/on_Snapshot__47_claudechrome.js:38, parseArgs is imported from chrome_utils.js, but chrome_utils.js itself re-exports the parseArgs it imports from base/utils.js. Meanwhile emitArchiveResult is correctly imported from base/utils.js at line 33. This isn't a runtime bug (the function works fine either way since it's the same function), but it's inconsistent with the pattern established by other refactored plugins in this PR (e.g., dom, pdf, screenshot) which import parseArgs from base/utils.js and chrome-specific functions from chrome_utils.js.

Suggested change
const {
getEnv,
getEnvBool,
getEnvInt,
parseArgs,
readCdpUrl,
connectToPage,
waitForPageLoaded,
} = require('../chrome/chrome_utils.js');
const {
getEnv,
getEnvBool,
getEnvInt,
parseArgs,
emitArchiveResult,
} = require('../base/utils.js');
const {
readCdpUrl,
connectToPage,
waitForPageLoaded,
} = require('../chrome/chrome_utils.js');
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +33 to +42
const { emitArchiveResult } = require('../base/utils.js');
const {
getEnv,
getEnvBool,
getEnvInt,
parseArgs,
readCdpUrl,
connectToPage,
waitForPageLoaded,
} = require('../chrome/chrome_utils.js');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 emitArchiveResult imported from base/utils.js but also imported duplicate getEnv/getEnvBool/getEnvInt/parseArgs from chrome_utils.js

In claudechrome/on_Snapshot__47_claudechrome.js, emitArchiveResult is imported from ../base/utils.js (line 33), but getEnv, getEnvBool, getEnvInt, and parseArgs are imported from ../chrome/chrome_utils.js (lines 35-42). While chrome_utils.js re-exports these from base/utils.js so it works at runtime, the emitArchiveResult function is NOT re-exported by chrome_utils.js — that's why it must come from base/utils.js. This inconsistency is cosmetic but could confuse future maintainers.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

- name: Discover test files
id: set-matrix
run: |
chrome_test_pattern='ensure_chrome_test_prereqs|ensure_chromium_and_puppeteer_installed|require_chrome_runtime|chrome_session\(|CHROME_NAVIGATE_HOOK'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Workflow indentation error: chrome_test_pattern variable defined outside run: block's indentation scope

In .github/workflows/test-parallel.yml:35, the chrome_test_pattern variable assignment is indented at 10 spaces while the rest of the run: block content is at 12 spaces (the plugin_tests= line at line 36 is at 12 spaces). This means chrome_test_pattern is syntactically outside the shell script body for the step. In YAML multiline run: blocks, all lines need consistent indentation relative to the block scalar indicator. The line at 10 spaces will still be included in the shell script (YAML strips leading whitespace based on the first content line's indentation), but the actual shell variable will be set correctly since bash doesn't care about indentation. So this is cosmetic rather than a runtime bug.

Suggested change
chrome_test_pattern='ensure_chrome_test_prereqs|ensure_chromium_and_puppeteer_installed|require_chrome_runtime|chrome_session\(|CHROME_NAVIGATE_HOOK'
chrome_test_pattern='ensure_chrome_test_prereqs|ensure_chromium_and_puppeteer_installed|require_chrome_runtime|chrome_session\(|CHROME_NAVIGATE_HOOK'
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

17 issues found across 104 files (changes from recent commits).

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="abx_plugins/plugins/pip/on_Binary__11_pip_install.py">

<violation number="1" location="abx_plugins/plugins/pip/on_Binary__11_pip_install.py:168">
P2: Pass the chosen `LIB_DIR` into the permission lock-down helper; as written, custom `LIB_DIR` installs are left unprotected because the helper always falls back to `~/.config/abx/lib`.</violation>
</file>

<file name="abx_plugins/plugins/base/utils.js">

<violation number="1" location="abx_plugins/plugins/base/utils.js:56">
P2: Comma-splitting array env vars will corrupt arguments that legitimately contain commas, such as Chrome flags.</violation>
</file>

<file name="abx_plugins/plugins/base/utils.py">

<violation number="1" location="abx_plugins/plugins/base/utils.py:346">
P1: Permissions do not enforce the stated read-only constraint on `lib/`. `0o755` (dirs) and `0o644`/`0o755` (files) both grant the owner write access. Since the owner is set to the same uid that snapshot hooks run as, those hooks can still modify installed binaries. Use `0o555` for directories and `0o444`/`0o555` for files to actually make `lib/` read-only for the data-dir user.</violation>
</file>

<file name="README.md">

<violation number="1" location="README.md:123">
P2: These documented helper signatures don't match `base/utils.py`, so copying them will call the wrong API. In particular, `output_binary(...)` is documented as an installed-binary emitter, but the real helper takes `binproviders` and emits a dependency declaration.</violation>
</file>

<file name="abx_plugins/plugins/chrome/tests/chrome_test_helpers.py">

<violation number="1" location="abx_plugins/plugins/chrome/tests/chrome_test_helpers.py:80">
P2: Import the shared test helpers via `abx_plugins.plugins.base.test_utils` instead of appending to `sys.path` and importing top-level `base`.</violation>
</file>

<file name="abx_plugins/plugins/archivedotorg/on_Snapshot__08_archivedotorg.bg.py">

<violation number="1" location="abx_plugins/plugins/archivedotorg/on_Snapshot__08_archivedotorg.bg.py:97">
P2: Preserve the robots.txt/manual-retry path for HTTPError responses. Right now blocked Wayback submissions can be reported as failed before `RobotAccessControlException` is inspected.</violation>
</file>

<file name="abx_plugins/plugins/claudechrome/tests/test_claudechrome.py">

<violation number="1" location="abx_plugins/plugins/claudechrome/tests/test_claudechrome.py:194">
P1: Custom agent: **Test quality checker**

This test no longer verifies graceful failure: it passes even when the hook crashes before emitting the required failed `ArchiveResult` JSONL.</violation>

<violation number="2" location="abx_plugins/plugins/claudechrome/tests/test_claudechrome.py:203">
P2: Guard this integration test behind an explicit API-key check. As written it runs by default, but the hook fails immediately without `ANTHROPIC_API_KEY` and otherwise makes a live Anthropic request.</violation>

<violation number="3" location="abx_plugins/plugins/claudechrome/tests/test_claudechrome.py:270">
P1: Custom agent: **Test quality checker**

This integration test is effectively a smoke test: it never verifies that Claude actually clicked `Show More` and changed the page state, so a no-op run can still pass.</violation>
</file>

<file name="abx_plugins/plugins/git/on_Snapshot__05_git.bg.py">

<violation number="1" location="abx_plugins/plugins/git/on_Snapshot__05_git.bg.py:68">
P2: Guard `load_config()` here or preserve the old fallback behavior for malformed env values. As written, an empty `GIT_TIMEOUT` or non-JSON `GIT_ARGS` now aborts the hook with `ValidationError` instead of falling back to defaults.</violation>
</file>

<file name="pyproject.toml">

<violation number="1" location="pyproject.toml:24">
P2: Removing `requests` from the main dependency set leaves the packaged test suite without a required import, so a plain install can no longer run the bundled tests successfully.</violation>
</file>

<file name="abx_plugins/plugins/defuddle/tests/test_defuddle.py">

<violation number="1" location="abx_plugins/plugins/defuddle/tests/test_defuddle.py:197">
P2: This change weakens the test: it now accepts extra JSONL records instead of verifying the hook emits exactly one failure result.</violation>
</file>

<file name=".github/workflows/test-parallel.yml">

<violation number="1" location=".github/workflows/test-parallel.yml:124">
P1: This step hardcodes the action's `/usr/bin/chromium` default instead of verifying the `CHROME_BINARY` path the tests actually provision. Chrome-dependent jobs can now fail before pytest installs Chromium.</violation>
</file>

<file name=".github/actions/verify-chromium-launch/action.yml">

<violation number="1" location=".github/actions/verify-chromium-launch/action.yml:24">
P1: Custom agent: **Test quality checker**

Fail this Chromium verifier on the first failed launch instead of retrying it to green; the retry loop violates the test-quality rule's no-flaky-tests / retry-logic clause by allowing an intermittent browser startup failure to pass CI.</violation>

<violation number="2" location=".github/actions/verify-chromium-launch/action.yml:98">
P2: Poll for the page target (or create one) instead of failing on the first `Target.getTargets` call.</violation>

<violation number="3" location=".github/actions/verify-chromium-launch/action.yml:245">
P2: Stop waiting for the full timeout after Chromium has already exited.</violation>
</file>

<file name="abx_plugins/plugins/archivedotorg/tests/test_archivedotorg.py">

<violation number="1" location="abx_plugins/plugins/archivedotorg/tests/test_archivedotorg.py:142">
P1: Custom agent: **Test quality checker**

`test_handles_timeout` no longer tests timeout handling; it now passes on any archive.org error, so regressions in the dedicated timeout path can slip through.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

for dirpath, _dirnames, filenames in os.walk(lib_dir):
dp = Path(dirpath)
_chown_if_needed(dp, target_uid, target_gid)
dp.chmod(0o755) # rwxr-xr-x
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 15, 2026

Choose a reason for hiding this comment

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

P1: Permissions do not enforce the stated read-only constraint on lib/. 0o755 (dirs) and 0o644/0o755 (files) both grant the owner write access. Since the owner is set to the same uid that snapshot hooks run as, those hooks can still modify installed binaries. Use 0o555 for directories and 0o444/0o555 for files to actually make lib/ read-only for the data-dir user.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abx_plugins/plugins/base/utils.py, line 346:

<comment>Permissions do not enforce the stated read-only constraint on `lib/`. `0o755` (dirs) and `0o644`/`0o755` (files) both grant the owner write access. Since the owner is set to the same uid that snapshot hooks run as, those hooks can still modify installed binaries. Use `0o555` for directories and `0o444`/`0o555` for files to actually make `lib/` read-only for the data-dir user.</comment>

<file context>
@@ -0,0 +1,382 @@
+    for dirpath, _dirnames, filenames in os.walk(lib_dir):
+        dp = Path(dirpath)
+        _chown_if_needed(dp, target_uid, target_gid)
+        dp.chmod(0o755)  # rwxr-xr-x
+        for fname in filenames:
+            fp = dp / fname
</file context>
Fix with Cubic


- name: Verify Chromium launch
if: ${{ matrix.test.needs_chromium }}
uses: ./.github/actions/verify-chromium-launch
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 15, 2026

Choose a reason for hiding this comment

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

P1: This step hardcodes the action's /usr/bin/chromium default instead of verifying the CHROME_BINARY path the tests actually provision. Chrome-dependent jobs can now fail before pytest installs Chromium.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/test-parallel.yml, line 124:

<comment>This step hardcodes the action's `/usr/bin/chromium` default instead of verifying the `CHROME_BINARY` path the tests actually provision. Chrome-dependent jobs can now fail before pytest installs Chromium.</comment>

<file context>
@@ -114,6 +119,10 @@ jobs:
 
+      - name: Verify Chromium launch
+        if: ${{ matrix.test.needs_chromium }}
+        uses: ./.github/actions/verify-chromium-launch
+
       - name: Run test - ${{ matrix.test.name }}
</file context>
Fix with Cubic

set -euo pipefail
max_attempts="${{ inputs.max-attempts }}"
attempt=1
while [ "$attempt" -le "$max_attempts" ]; do
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 15, 2026

Choose a reason for hiding this comment

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

P1: Custom agent: Test quality checker

Fail this Chromium verifier on the first failed launch instead of retrying it to green; the retry loop violates the test-quality rule's no-flaky-tests / retry-logic clause by allowing an intermittent browser startup failure to pass CI.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/actions/verify-chromium-launch/action.yml, line 24:

<comment>Fail this Chromium verifier on the first failed launch instead of retrying it to green; the retry loop violates the test-quality rule's no-flaky-tests / retry-logic clause by allowing an intermittent browser startup failure to pass CI.</comment>

<file context>
@@ -0,0 +1,279 @@
+        set -euo pipefail
+        max_attempts="${{ inputs.max-attempts }}"
+        attempt=1
+        while [ "$attempt" -le "$max_attempts" ]; do
+          if [ -n "${{ inputs.chrome-path }}" ]; then
+            pkill -f "${{ inputs.chrome-path }}" >/dev/null 2>&1 || true
</file context>
Fix with Cubic

assert result_json, "Should emit failed JSONL on error"
assert result_json["status"] == "failed", result_json
assert "timed out" in result_json["output_str"].lower(), result_json
assert result_json["output_str"], "Should include error description"
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 15, 2026

Choose a reason for hiding this comment

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

P1: Custom agent: Test quality checker

test_handles_timeout no longer tests timeout handling; it now passes on any archive.org error, so regressions in the dedicated timeout path can slip through.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abx_plugins/plugins/archivedotorg/tests/test_archivedotorg.py, line 142:

<comment>`test_handles_timeout` no longer tests timeout handling; it now passes on any archive.org error, so regressions in the dedicated timeout path can slip through.</comment>

<file context>
@@ -145,17 +133,13 @@ def test_handles_timeout():
+            assert result_json, "Should emit failed JSONL on error"
             assert result_json["status"] == "failed", result_json
-            assert "timed out" in result_json["output_str"].lower(), result_json
+            assert result_json["output_str"], "Should include error description"
 
 
</file context>
Fix with Cubic

Comment thread abx_plugins/plugins/claudechrome/tests/test_claudechrome.py
timeout = get_env_int("GIT_TIMEOUT") or get_env_int("TIMEOUT", 120)
git_args = get_env_array("GIT_ARGS", ["clone", "--depth=1", "--recursive"])
git_args_extra = get_env_array("GIT_ARGS_EXTRA", [])
config = load_config()
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 15, 2026

Choose a reason for hiding this comment

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

P2: Guard load_config() here or preserve the old fallback behavior for malformed env values. As written, an empty GIT_TIMEOUT or non-JSON GIT_ARGS now aborts the hook with ValidationError instead of falling back to defaults.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abx_plugins/plugins/git/on_Snapshot__05_git.bg.py, line 68:

<comment>Guard `load_config()` here or preserve the old fallback behavior for malformed env values. As written, an empty `GIT_TIMEOUT` or non-JSON `GIT_ARGS` now aborts the hook with `ValidationError` instead of falling back to defaults.</comment>

<file context>
@@ -86,9 +65,10 @@ def clone_git(url: str, binary: str) -> tuple[bool, str | None, str]:
-    timeout = get_env_int("GIT_TIMEOUT") or get_env_int("TIMEOUT", 120)
-    git_args = get_env_array("GIT_ARGS", ["clone", "--depth=1", "--recursive"])
-    git_args_extra = get_env_array("GIT_ARGS_EXTRA", [])
+    config = load_config()
+    timeout = config.GIT_TIMEOUT
+    git_args = config.GIT_ARGS
</file context>
Fix with Cubic

Comment thread pyproject.toml
Comment on lines +24 to 27
"pydantic-settings>=2.0.0",
"pyright>=1.1.408",
"pytest>=9.0.2",
"pytest-httpserver>=1.1.0",
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 15, 2026

Choose a reason for hiding this comment

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

P2: Removing requests from the main dependency set leaves the packaged test suite without a required import, so a plain install can no longer run the bundled tests successfully.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pyproject.toml, line 24:

<comment>Removing `requests` from the main dependency set leaves the packaged test suite without a required import, so a plain install can no longer run the bundled tests successfully.</comment>

<file context>
@@ -21,10 +21,10 @@ classifiers = [
 dependencies = [
     "abx-pkg>=0.6.3",
     "feedparser>=6.0.0",
+    "pydantic-settings>=2.0.0",
     "pyright>=1.1.408",
     "pytest>=9.0.2",
</file context>
Suggested change
"pydantic-settings>=2.0.0",
"pyright>=1.1.408",
"pytest>=9.0.2",
"pytest-httpserver>=1.1.0",
"pydantic-settings>=2.0.0",
"pyright>=1.1.408",
"pytest>=9.0.2",
"pytest-httpserver>=1.1.0",
"requests>=2.32.5",
Fix with Cubic

Comment on lines +197 to +198
record = parse_jsonl_output(result.stdout)
assert record, "Should have ArchiveResult JSONL output"
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 15, 2026

Choose a reason for hiding this comment

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

P2: This change weakens the test: it now accepts extra JSONL records instead of verifying the hook emits exactly one failure result.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abx_plugins/plugins/defuddle/tests/test_defuddle.py, line 197:

<comment>This change weakens the test: it now accepts extra JSONL records instead of verifying the hook emits exactly one failure result.</comment>

<file context>
@@ -198,11 +194,8 @@ def test_reports_missing_dependency_when_not_installed():
-        ]
-        assert len(jsonl_lines) == 1
-        record = json.loads(jsonl_lines[0])
+        record = parse_jsonl_output(result.stdout)
+        assert record, "Should have ArchiveResult JSONL output"
         assert record["type"] == "ArchiveResult"
</file context>
Suggested change
record = parse_jsonl_output(result.stdout)
assert record, "Should have ArchiveResult JSONL output"
jsonl_lines = [
line for line in result.stdout.strip().split("\n") if line.strip().startswith("{")
]
assert len(jsonl_lines) == 1
record = json.loads(jsonl_lines[0])
Fix with Cubic


ws.addEventListener("open", async () => {
try {
const targets = await send("Target.getTargets");
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 15, 2026

Choose a reason for hiding this comment

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

P2: Poll for the page target (or create one) instead of failing on the first Target.getTargets call.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/actions/verify-chromium-launch/action.yml, line 98:

<comment>Poll for the page target (or create one) instead of failing on the first `Target.getTargets` call.</comment>

<file context>
@@ -0,0 +1,279 @@
+
+            ws.addEventListener("open", async () => {
+              try {
+                const targets = await send("Target.getTargets");
+                const pageTarget = (targets.targetInfos || []).find(
+                  (target) => target.type === "page" && target.url === "about:blank",
</file context>
Fix with Cubic


proc.stdout.on("data", onData("stdout"));
proc.stderr.on("data", onData("stderr"));
proc.on("exit", (code, signal) => {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 15, 2026

Choose a reason for hiding this comment

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

P2: Stop waiting for the full timeout after Chromium has already exited.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/actions/verify-chromium-launch/action.yml, line 245:

<comment>Stop waiting for the full timeout after Chromium has already exited.</comment>

<file context>
@@ -0,0 +1,279 @@
+
+            proc.stdout.on("data", onData("stdout"));
+            proc.stderr.on("data", onData("stderr"));
+            proc.on("exit", (code, signal) => {
+              if (!closed && !wsUrl) {
+                closed = true;
</file context>
Fix with Cubic

cubic-dev-ai[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 20 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/test_chrome_runtime_fixture.py">

<violation number="1" location="tests/test_chrome_runtime_fixture.py:6">
P3: Use pytest's public `pytest.fail.Exception` instead of importing the private `_pytest.outcomes.Failed` type.</violation>

<violation number="2" location="tests/test_chrome_runtime_fixture.py:19">
P1: Custom agent: **Test quality checker**

These tests violate the test-quality rule by monkeypatching `Binary.load` and calling `require_chrome_runtime.__wrapped__()`, so they only verify mocked fixture internals instead of the real runtime-resolution behavior.</violation>
</file>

<file name="abx_plugins/plugins/chrome/chrome_utils.js">

<violation number="1" location="abx_plugins/plugins/chrome/chrome_utils.js:1507">
P2: Use the default lib directory here instead of requiring `LIB_DIR` to be set, or hook-installed Chromium in `~/.config/abx/lib` will not be discovered.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

loaded.append(self.name)
return self

monkeypatch.setattr(Binary, "load", fake_load)
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 15, 2026

Choose a reason for hiding this comment

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

P1: Custom agent: Test quality checker

These tests violate the test-quality rule by monkeypatching Binary.load and calling require_chrome_runtime.__wrapped__(), so they only verify mocked fixture internals instead of the real runtime-resolution behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/test_chrome_runtime_fixture.py, line 19:

<comment>These tests violate the test-quality rule by monkeypatching `Binary.load` and calling `require_chrome_runtime.__wrapped__()`, so they only verify mocked fixture internals instead of the real runtime-resolution behavior.</comment>

<file context>
@@ -0,0 +1,40 @@
+        loaded.append(self.name)
+        return self
+
+    monkeypatch.setattr(Binary, "load", fake_load)
+
+    conftest.require_chrome_runtime.__wrapped__()
</file context>
Fix with Cubic

Comment thread abx_plugins/plugins/chrome/chrome_utils.js
import conftest

import pytest
from _pytest.outcomes import Failed
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 15, 2026

Choose a reason for hiding this comment

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

P3: Use pytest's public pytest.fail.Exception instead of importing the private _pytest.outcomes.Failed type.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/test_chrome_runtime_fixture.py, line 6:

<comment>Use pytest's public `pytest.fail.Exception` instead of importing the private `_pytest.outcomes.Failed` type.</comment>

<file context>
@@ -0,0 +1,40 @@
+import conftest
+
+import pytest
+from _pytest.outcomes import Failed
+
+from abx_pkg import Binary
</file context>
Fix with Cubic

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 37 additional findings in Devin Review.

Open in Devin Review

Comment on lines +97 to +100
except HTTPError as e:
if e.code >= 400:
return False, None, f"HTTP {e.code}"
return False, None, f"HTTPError: {e}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 archivedotorg: Migration from requests to urllib causes robots.txt-blocked URLs to be treated as hard failures

The migration from requests.get() to urllib.request.urlopen() changes how HTTP 4xx/5xx responses are handled. urlopen() raises HTTPError for 4xx/5xx status codes before the response body can be inspected, while requests.get() returns the response object regardless of status code. The old code checked the response body for RobotAccessControlException (line 86 in new code) before checking the status code, treating robots.txt-blocked URLs as soft successes (saving the submit URL for manual retry). With urlopen, a 403 from archive.org's robots.txt blocking raises HTTPError at line 58, which is caught at line 97 and returned as a hard failure (False, None, "HTTP 403"). The RobotAccessControlException body check at line 86 is now unreachable for 4xx/5xx responses.

Fix approach

Read the response body from the HTTPError object (which is file-like) and check for RobotAccessControlException before returning a hard failure:

except HTTPError as e:
    body = e.read().decode('utf-8', errors='replace')
    if 'RobotAccessControlException' in body:
        Path(OUTPUT_FILE).write_text(submit_url, encoding='utf-8')
        log('Blocked by robots.txt, saved submit URL for manual retry')
        return True, OUTPUT_FILE, ''
    if e.code >= 400:
        return False, None, f'HTTP {e.code}'
Suggested change
except HTTPError as e:
if e.code >= 400:
return False, None, f"HTTP {e.code}"
return False, None, f"HTTPError: {e}"
except HTTPError as e:
err_body = e.read().decode("utf-8", errors="replace")
if "RobotAccessControlException" in err_body:
Path(OUTPUT_FILE).write_text(submit_url, encoding="utf-8")
log("Blocked by robots.txt, saved submit URL for manual retry")
return True, OUTPUT_FILE, "" # Consider this a soft success
if e.code >= 400:
return False, None, f"HTTP {e.code}"
return False, None, f"HTTPError: {e}"
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

cubic-dev-ai[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 39 additional findings in Devin Review.

Open in Devin Review

Comment on lines +101 to 102
except TimeoutError:
return False, None, f"Request timed out after {timeout} seconds"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Unreachable except TimeoutError handler — urllib wraps timeouts in URLError

After migrating from requests to urllib, the except TimeoutError clause is dead code. When urllib.request.urlopen encounters a socket timeout, it wraps the socket.timeout (which IS TimeoutError) inside a URLError via its internal except OSError as err: raise URLError(err) handler (urllib/request.py:do_open). The raised exception is URLError, not TimeoutError, so the except TimeoutError at line 101 never fires. Timeouts are instead caught by except URLError as e at line 103, producing the message "URLError: <urlopen error timed out>" instead of the intended user-friendly "Request timed out after {timeout} seconds".

Suggested change
except TimeoutError:
return False, None, f"Request timed out after {timeout} seconds"
except URLError as e:
if isinstance(e.reason, TimeoutError):
return False, None, f"Request timed out after {timeout} seconds"
if isinstance(e.reason, HTTPError) or (hasattr(e, 'code') and e.code >= 400):
return False, None, f"HTTP error: {e}"
return False, None, f"URLError: {e.reason}"
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

claude added 5 commits March 15, 2026 08:28
- claudecode: Base plugin that installs Claude Code CLI from npm and provides
  shared utilities for spawning Claude Code with system prompts describing the
  crawl/snapshot directory layout and metadata. Configurable via ANTHROPIC_API_KEY,
  CLAUDECODE_MODEL, CLAUDECODE_MAX_TURNS, etc.

- claudecodeextract: Runs at snapshot priority 58 with a user-configurable prompt
  (CLAUDECODEEXTRACT_PROMPT) to generate derived content from existing extractor
  outputs. Default prompt generates clean Markdown from best available source.

- claudecodecleanup: Runs at snapshot priority 99 (end of pipeline) to analyze
  all extractor outputs, identify duplicate/redundant files, compare quality,
  and delete inferior versions. Configurable via CLAUDECODECLEANUP_PROMPT.

All three follow the established plugin patterns (config.json, templates,
install hooks, snapshot hooks, emit_archive_result JSONL output).

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
…ugins

28 tests covering:
- Hook scripts exist and are executable
- Config JSON schemas are valid with correct properties
- Plugin dependencies declared (required_plugins: claudecode)
- Install hook emits correct Binary JSONL for @anthropic-ai/claude-code
- Hooks skip cleanly when ENABLED=false
- Hooks fail gracefully with missing API key or binary
- System prompt builder includes snapshot metadata and extractor outputs
- Cleanup hook runs at priority 99 with higher max_turns default

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
Add integration test classes that actually run Claude Code CLI against
real snapshot data when ANTHROPIC_API_KEY is available:

- TestClaudeCodeIntegration: tests simple prompt response, system prompt
  with snapshot context, and file writing via Claude Code
- TestClaudeCodeExtractIntegration: runs full extract hook against a
  fake snapshot with readability/htmltotext/dom outputs, tests default
  markdown generation and custom prompt (JSON extraction)
- TestClaudeCodeCleanupIntegration: runs cleanup hook against a snapshot
  with intentional duplicates, verifies report generation and that
  hashes/ is preserved

Tests auto-skip with clear message when prerequisites are missing.
To run: ANTHROPIC_API_KEY=sk-... pytest -v -k Integration

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
…ompts

- All three plugins now default to disabled (opt-in only)
- Stricter system prompts: cleanup hook now explicitly requires writing
  cleanup_report.txt with full path, reinforced in both system prompt and
  user prompt
- Extract hook system prompt stricter about saving files to output dir only
- Added test_install_hook_skips_by_default for claudecode base plugin
- Fixed integration test max_turns (haiku needs more turns for tool use)

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
claude and others added 20 commits March 15, 2026 08:28
… reorder

- Fix iframe sandbox security: remove allow-same-origin + allow-scripts (all 3 plugins)
- Switch env var filtering from allowlist to denylist approach in claudecode_utils
- Add session.json logging as artifact in extract and cleanup output dirs
- Move cleanup plugin from priority 99 to 92 (before hashes at 93)
- Honor CLAUDECODE_BINARY env var in install hook
- Create output dir after building system prompt (avoid self-listing)
- Exclude response.txt/session.json from extract output file count
- Fix docstring defaults to match code (ENABLED=false)
- Strengthen error assertions in tests

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
- Remove all skipif gates from integration tests (always run in CI)
- Fix max_turns test to compare cleanup default against extract default
- Strengthen extract integration test to verify content.md generation
- Add cleanup_report.txt assertion to test_cleanup_produces_report
- Fix test_cleanup_preserves_hashes to test real deletion with hashes preserved
- Remove unused shutil imports

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
…fy no-skip policy

- Fix cleanup hook docstring referencing old priority 99 (now 92)
- Add assertion verifying broken_extractor/ was actually deleted
- Add docstring comments clarifying intentional lack of skip decorators

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
… naming

- Include stderr detail in ArchiveResult error messages (not just exit code)
- Exclude session.json from cleanup fallback output file count
- Use basename for Binary record name when CLAUDECODE_BINARY is an absolute path
- Tighten missing-binary assertions to require "not found" specifically
- Clarify session log format comment in claudecode_utils.py
- Document that CLAUDECODE_ENABLED must be set for child plugins to work

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
- Add CRITICAL RESTRICTION in system prompts: must not operate outside SNAP_DIR
- Cleanup plugin: broaden allowed tools to full set (Read, Write, Edit, Bash,
  Glob, Grep) since it needs flexibility within the snapshot dir
- Extract plugin: clarify read-only access to snapshot, write-only to output dir
- Path scoping enforced via system prompt (Claude Code --allowedTools cannot
  restrict by path, only by command name)

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
Each README covers configuration env vars, binary dependencies,
hook priorities, permissions/scope, output files, and usage examples.

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
New plugin that installs and drives the official Claude for Chrome extension
(fcoeoabgfenejglbffodgkkbkcdhcgfn) to interact with pages during archiving.

Hooks:
- on_Crawl__84: Install extension from Chrome Web Store (like twocaptcha/ublock)
- on_Crawl__96: Inject ANTHROPIC_API_KEY into extension storage after Chrome launch
- on_Snapshot__47: Run user-configurable prompt on page via extension side panel
  (after infiniscroll@45, before singlefile@50)

Features:
- Configurable prompt via CLAUDECHROME_PROMPT env var
- Downloads triggered by Claude moved from chrome_downloads/ to output dir
- Conversation log saved as JSON + human-readable text
- Default prompt: click all expand/show-more buttons

Includes config.json, templates, tests (8 passing), and README.

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
Instead of trying to puppet the Claude for Chrome extension's side panel UI
(fragile, requires OAuth login), the snapshot hook now directly implements
the computer-use agentic loop:

1. Take screenshot via CDP Page.captureScreenshot
2. Send to Claude via Anthropic Messages API (computer_20250124 tool)
3. Execute actions (left_click, type, key, scroll, etc.) via puppeteer
4. Repeat until Claude responds text-only or max iterations reached

Uses curl for API calls (reliable proxy support in all environments).

Verified end-to-end: Claude successfully clicked a "Show More" button on
a test page, revealed hidden content, and reported the secret message.
Screenshots confirm page modification (19KB initial -> 28KB after click).

Also adds CLAUDECHROME_MAX_ACTIONS config and fixes CHROME_SESSION_DIR path.

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
…anup.py

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
….bg.py

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
- claudecode_utils.py: remove local get_env, get_env_bool, get_env_int,
  emit_archive_result; import from base.utils instead
- install hook: remove local get_env, get_env_bool, output_binary;
  import from base.utils and use its output_binary with overrides param
- claudecodeextract/claudecodecleanup: import get_env*, emit_archive_result
  directly from base.utils; keep build_system_prompt/run_claude_code from
  claudecode_utils
- claudechrome snapshot hook: import emitArchiveResult from base/utils.js
  and replace inline JSON.stringify calls

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
…echrome integration test

- All 4 test files now import get_plugin_dir, get_hook_script,
  parse_jsonl_output, run_hook from base.test_utils instead of
  chrome_test_helpers (reducing coupling)
- Replace manual subprocess calls + inline JSONL parsing with
  run_hook() and parse_jsonl_output() helpers
- claudechrome: replace placeholder test_full_pipeline_with_chrome_session
  with real integration test using chrome_session context manager,
  httpserver test page, and full output verification
- claudechrome: add test_snapshot_hook_fails_without_chrome_session
- Integration test class uses @pytest.mark.usefixtures("ensure_chrome_test_prereqs")
  instead of module-level pytestmark so unit tests run without Chrome

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
…ome sandbox/UID handling

- Add ensure_claude_code_prereqs session fixture that validates claude
  binary in PATH and ANTHROPIC_API_KEY is set, with clear error messages
- Override ensure_chromium_and_puppeteer_installed to scan well-known
  paths for pre-installed Chromium before falling back to hook-based
  install (fixes UID 1001 passwd entry issue in containers)
- Auto-disable Chrome sandbox when running as root (CHROME_SANDBOX=false)
- All 4 integration test classes now use @pytest.mark.usefixtures for
  their respective prereqs
- All 46 tests pass (38 unit + 8 integration)

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
…er) works

The ensure_chromium_and_puppeteer_installed fixture now goes straight
through install_chromium_with_hooks with no manual path scanning.
The NpmProvider UID 1001 fix is landing upstream separately.

Only addition over upstream fixture: auto-disable Chrome sandbox when
running as root (CHROME_SANDBOX=false).

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
findChromium() now checks LIB_DIR/chrome-linux/chrome and
LIB_DIR/browsers/chrome/chrome before falling back to system
locations. This lets _resolve_existing_chromium find Chromium
installed by the hook-based install pipeline, avoiding redundant
downloads from storage.googleapis.com.

Also confirmed abx-pkg 0.6.4 UID/EUID fix resolves the
NpmProvider passwd entry issue (pwd.getpwuid(1001)).

All 46 tests pass (38 unit + 8 integration).

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
…LAUDECODE_BINARY, stop env propagation

- Move get_lib_dir() call before monkeypatch.setenv("HOME") so chrome_utils.js
  sees the real home directory when resolving LIB_DIR
- Stop propagating NODE_MODULES_DIR/NODE_PATH/PATH from session fixture to
  os.environ — tests must not depend on execution order
- Honor CLAUDECODE_BINARY env var in ensure_claude_code_prereqs fixture
- Replace ensure_claude_code_prereqs with ensure_anthropic_api_key on
  claudechrome integration tests (claudechrome doesn't use Claude CLI)
- Add ensure_anthropic_api_key session fixture for plugins that call the
  Anthropic API directly

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
… fields

- Rename claudecode and claudechrome install hooks from .bg. to .finite.bg.
  to match the new naming convention (finite bg hooks run once and terminate)
- Add required metadata fields (title, description, required_plugins,
  required_binaries, output_mimetypes) to all four Claude plugin config.jsons
- Update README references to use new hook filenames

https://claude.ai/code/session_013zgn8SbbiwJJAxC3UzAHZ6
@pirate pirate force-pushed the claude/create-new-plugins-p19P7 branch from 7657a1b to a2b0a17 Compare March 15, 2026 08:31
@pirate pirate merged commit d0f8162 into main Mar 15, 2026
57 of 62 checks passed
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.

2 participants