diff --git a/Dockerfile b/Dockerfile
index 22addaa1..0d32a44f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -312,7 +312,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH$T
echo "[+] Installing plugin runtime dependencies into $LIB_DIR..." \
&& apt-get update -qq \
&& if [ "$TARGETARCH" = "arm64" ]; then \
- abxpkg install --binproviders=playwright --bin-dir="$LIB_DIR/env/bin" chromium; \
+ abxpkg install --binproviders=playwright --bin-dir="$LIB_DIR/env/bin" --overrides='{"playwright":{"install_args":["chrome-beta"]}}' chromium; \
fi \
&& PUID=0 PGID=0 abx-dl plugins --install \
&& find "$LIB_DIR" "$DATA_DIR"/personas -type d -name __pycache__ -prune -exec rm -rf {} + \
diff --git a/archivebox/cli/archivebox_process.py b/archivebox/cli/archivebox_process.py
index 4df39c75..d307b22d 100644
--- a/archivebox/cli/archivebox_process.py
+++ b/archivebox/cli/archivebox_process.py
@@ -54,7 +54,7 @@ def list_processes(
is_tty = sys.stdout.isatty()
- queryset = Process.objects.all().select_related("binary", "machine").order_by("-start_ts")
+ queryset = Process.objects.all().select_related("binary", "machine").order_by("-started_at", "-created_at")
# Apply filters
filter_kwargs = {}
diff --git a/archivebox/config/common.py b/archivebox/config/common.py
index f48c925f..8db8002d 100644
--- a/archivebox/config/common.py
+++ b/archivebox/config/common.py
@@ -483,15 +483,16 @@ def get_config(
Priority (highest to lowest):
1. Explicit overrides
- 2. Per-snapshot config and output path
- 3. Per-crawl config and output path
- 4. Per-user config
- 5. Per-persona derived config
- 6. Current machine derived config
- 7. Environment variables
- 8. Config file (ArchiveBox.conf)
- 9. Plugin schema defaults
- 10. Core config defaults
+ 2. Per-ArchiveResult config
+ 3. Per-snapshot config and output path
+ 4. Per-crawl config and output path
+ 5. Per-user config
+ 6. Per-persona derived config
+ 7. Current machine derived config
+ 8. Environment variables
+ 9. Config file (ArchiveBox.conf)
+ 10. Plugin schema defaults
+ 11. Core config defaults
"""
if snapshot is None and archiveresult is not None:
snapshot = archiveresult.snapshot
@@ -559,6 +560,9 @@ def get_config(
if snapshot is not None:
scope_overrides["SNAP_DIR"] = snapshot.output_dir
+ if archiveresult is not None and archiveresult.config:
+ scope_overrides.update(archiveresult.config)
+
if overrides:
scope_overrides.update(overrides)
diff --git a/archivebox/core/admin_archiveresults.py b/archivebox/core/admin_archiveresults.py
index 2b7eb18d..529f108b 100644
--- a/archivebox/core/admin_archiveresults.py
+++ b/archivebox/core/admin_archiveresults.py
@@ -34,7 +34,7 @@ from archivebox.core.models import ArchiveResult, Snapshot
def _get_replay_source_url(result: ArchiveResult) -> str:
- process = getattr(result, "process", None)
+ process = result.process_record
return str(getattr(process, "url", None) or result.snapshot.url or "")
@@ -134,20 +134,21 @@ def render_archiveresults_list(archiveresults_qs, limit=50, config=None):
# Format timestamp
end_time = result.end_ts.strftime("%Y-%m-%d %H:%M:%S") if result.end_ts else "-"
+ process = result.process_record
process_display = "-"
- if result.process_id and result.process:
+ if process:
process_display = f'''
- {result.process.pid or "-"}
+ title="View process">{process.pid or "-"}
'''
machine_display = "-"
- if result.process_id and result.process and result.process.machine_id:
+ if process and process.machine_id:
machine_display = f'''
- {result.process.machine.hostname}
+ title="View machine">{process.machine.hostname}
'''
# Truncate output for display
@@ -660,20 +661,22 @@ class ArchiveResultAdmin(BaseModelAdmin):
@admin.display(description="Process", ordering="process__pid")
def process_link(self, result):
- if not result.process_id:
+ process = result.process_record
+ if not process:
return "-"
- process_label = result.process.pid if result.process and result.process.pid else "-"
+ process_label = process.pid or "-"
return format_html(
'{}',
- reverse("admin:machine_process_change", args=[result.process_id]),
+ reverse("admin:machine_process_change", args=[process.id]),
process_label,
)
@admin.display(description="Machine", ordering="process__machine__hostname")
def machine_link(self, result):
- if not result.process_id or not result.process or not result.process.machine_id:
+ process = result.process_record
+ if not process or not process.machine_id:
return "-"
- machine = result.process.machine
+ machine = process.machine
return format_html(
'{} {}',
reverse("admin:machine_machine_change", args=[machine.id]),
diff --git a/archivebox/core/migrations/0039_alter_archiveresult_process.py b/archivebox/core/migrations/0039_alter_archiveresult_process.py
new file mode 100644
index 00000000..7277b6d3
--- /dev/null
+++ b/archivebox/core/migrations/0039_alter_archiveresult_process.py
@@ -0,0 +1,26 @@
+# Generated by Django 6.0.5 on 2026-05-27 20:11
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("core", "0038_snapshot_progress_idx"),
+ ("machine", "0015_process_progress_indexes"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="archiveresult",
+ name="process",
+ field=models.OneToOneField(
+ blank=True,
+ help_text="Process execution details for this archive result",
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="archiveresult",
+ to="machine.process",
+ ),
+ ),
+ ]
diff --git a/archivebox/core/models.py b/archivebox/core/models.py
index 4aaad2dd..e8612b18 100755
--- a/archivebox/core/models.py
+++ b/archivebox/core/models.py
@@ -1341,15 +1341,16 @@ class Snapshot(ModelWithOutputDir, ModelWithConfig, ModelWithNotes, ModelWithHea
# Write ArchiveResult records with their associated Binary and Process
# Use select_related to optimize queries
for ar in self.archiveresult_set.select_related("process__binary").order_by("start_ts"):
+ process = ar.process_record
# Write Binary record if not already written
- if ar.process and ar.process.binary and ar.process.binary_id not in binaries_seen:
- binaries_seen.add(ar.process.binary_id)
- f.write(json.dumps(ar.process.binary.to_json()) + "\n")
+ if process and process.binary and process.binary_id not in binaries_seen:
+ binaries_seen.add(process.binary_id)
+ f.write(json.dumps(process.binary.to_json()) + "\n")
# Write Process record if not already written
- if ar.process and ar.process_id not in processes_seen:
- processes_seen.add(ar.process_id)
- f.write(json.dumps(ar.process.to_json()) + "\n")
+ if process and process.id not in processes_seen:
+ processes_seen.add(process.id)
+ f.write(json.dumps(process.to_json()) + "\n")
# Write ArchiveResult record
f.write(json.dumps(ar.to_json()) + "\n")
@@ -3065,7 +3066,7 @@ class ArchiveResult(ModelWithOutputDir, ModelWithConfig, ModelWithNotes):
# Added POST-v0.9.0, will be added in a separate migration
process = models.OneToOneField(
"machine.Process",
- on_delete=models.PROTECT,
+ on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="archiveresult",
@@ -3137,12 +3138,15 @@ class ArchiveResult(ModelWithOutputDir, ModelWithConfig, ModelWithNotes):
record["output_size"] = self.output_size
if self.output_mimetypes:
record["output_mimetypes"] = self.output_mimetypes
+ if self.pwd:
+ record["pwd"] = self.pwd
if self.cmd:
record["cmd"] = self.cmd
if self.cmd_version:
record["cmd_version"] = self.cmd_version
- if self.process_id:
- record["process_id"] = str(self.process_id)
+ process = self.process_record
+ if process:
+ record["process_id"] = str(process.id)
return record
@staticmethod
@@ -3607,40 +3611,56 @@ class ArchiveResult(ModelWithOutputDir, ModelWithConfig, ModelWithNotes):
# these properties provide seamless access to Process data through ArchiveResult
# Uncommented after migration 3 completed - properties now active
+ @property
+ def process_record(self):
+ if not self.process_id:
+ return None
+ try:
+ return self.process
+ except ObjectDoesNotExist:
+ return None
+
@property
def pwd(self) -> str:
- """Working directory (from Process)."""
- return self.process.pwd if self.process_id else ""
+ """Working directory, derived from the snapshot/plugin path if the Process row is gone."""
+ process = self.process_record
+ return process.pwd if process and process.pwd else str(self.output_dir)
@property
def cmd(self) -> list:
"""Command array (from Process)."""
- return self.process.cmd if self.process_id else []
+ process = self.process_record
+ return process.cmd if process else []
@property
def cmd_version(self) -> str:
"""Command version (from Process.binary)."""
- return self.process.cmd_version if self.process_id else ""
+ process = self.process_record
+ return process.cmd_version if process else ""
@property
def binary(self):
"""Binary FK (from Process)."""
- return self.process.binary if self.process_id else None
+ process = self.process_record
+ return process.binary if process else None
@property
def iface(self):
"""Network interface FK (from Process)."""
- return self.process.iface if self.process_id else None
+ process = self.process_record
+ return process.iface if process else None
@property
def machine(self):
"""Machine FK (from Process)."""
- return self.process.machine if self.process_id else None
+ process = self.process_record
+ return process.machine if process else None
@property
def timeout(self) -> int:
"""Timeout in seconds (from Process)."""
- return self.process.timeout if self.process_id else 120
+ process = self.process_record
+ return process.timeout if process else 120
def save_search_index(self):
pass
@@ -3676,8 +3696,9 @@ class ArchiveResult(ModelWithOutputDir, ModelWithConfig, ModelWithNotes):
# Read and parse JSONL output from stdout.log
stdout_file = plugin_dir / "stdout.log"
records = []
- if self.process_id and self.process:
- records = extract_records_from_process(self.process)
+ process = self.process_record
+ if process:
+ records = extract_records_from_process(process)
if not records:
stdout = stdout_file.read_text() if stdout_file.exists() else ""
@@ -3703,9 +3724,9 @@ class ArchiveResult(ModelWithOutputDir, ModelWithConfig, ModelWithNotes):
# Update cmd fields
if hook_data.get("cmd"):
- if self.process_id:
- self.process.cmd = hook_data["cmd"]
- self.process.save()
+ if process:
+ process.cmd = hook_data["cmd"]
+ process.save()
self._set_binary_from_cmd(hook_data["cmd"])
# Note: cmd_version is derived from binary.version, not stored on Process
else:
@@ -3718,7 +3739,7 @@ class ArchiveResult(ModelWithOutputDir, ModelWithConfig, ModelWithNotes):
except Exception:
pass
- if is_background or (self.process_id and self.process and self.process.exit_code == 0):
+ if is_background or (process and process.exit_code == 0):
self.status = self.StatusChoices.SKIPPED
self.output_str = "Hook did not output ArchiveResult record"
else:
@@ -3822,9 +3843,10 @@ class ArchiveResult(ModelWithOutputDir, ModelWithConfig, ModelWithNotes):
).first()
if binary:
- if self.process_id:
- self.process.binary = binary
- self.process.save()
+ process = self.process_record
+ if process:
+ process.binary = binary
+ process.save()
return
# Fallback: match by binary name
@@ -3835,9 +3857,10 @@ class ArchiveResult(ModelWithOutputDir, ModelWithConfig, ModelWithNotes):
).first()
if binary:
- if self.process_id:
- self.process.binary = binary
- self.process.save()
+ process = self.process_record
+ if process:
+ process.binary = binary
+ process.save()
def _url_passes_filters(self, url: str) -> bool:
"""Check if URL passes URL_ALLOWLIST and URL_DENYLIST config filters.
diff --git a/archivebox/core/views.py b/archivebox/core/views.py
index 052238e8..6fca210e 100644
--- a/archivebox/core/views.py
+++ b/archivebox/core/views.py
@@ -917,13 +917,13 @@ class PublicIndexView(ListView):
def get_paginate_by(self, queryset):
runtime_config = getattr(self, "runtime_config", None)
if runtime_config is None:
- self.runtime_config = runtime_config = get_config()
+ self.runtime_config = runtime_config = _get_request_config(self.request, resolve_plugins=True)
return runtime_config.SNAPSHOTS_PER_PAGE
def get_context_data(self, **kwargs):
runtime_config = getattr(self, "runtime_config", None)
if runtime_config is None:
- self.runtime_config = runtime_config = get_config()
+ self.runtime_config = runtime_config = _get_request_config(self.request, resolve_plugins=True)
context = {
**super().get_context_data(**kwargs),
"VERSION": VERSION,
@@ -990,7 +990,7 @@ class PublicIndexView(ListView):
def get(self, *args, **kwargs):
if self.request.user.is_authenticated:
return redirect("/admin/core/snapshot/")
- if get_config().PUBLIC_INDEX:
+ if _get_request_config(self.request).PUBLIC_INDEX:
response = super().get(*args, **kwargs)
return response
else:
@@ -1012,7 +1012,7 @@ class AddView(UserPassesTestMixin, FormView):
return super().get_initial()
def test_func(self):
- return get_config().PUBLIC_ADD_VIEW or self.request.user.is_authenticated
+ return _get_request_config(self.request).PUBLIC_ADD_VIEW or self.request.user.is_authenticated
def _can_override_crawl_config(self) -> bool:
user = self.request.user
@@ -1032,7 +1032,8 @@ class AddView(UserPassesTestMixin, FormView):
def get_context_data(self, **kwargs):
from archivebox.personas.models import Persona
- required_search_plugin = f"search_backend_{get_config().SEARCH_BACKEND_ENGINE}".strip()
+ request_config = _get_request_config(self.request, resolve_plugins=True)
+ required_search_plugin = f"search_backend_{request_config.SEARCH_BACKEND_ENGINE}".strip()
plugin_configs = discover_plugin_configs()
sensitive_keys = {
str(config_key)
@@ -1062,7 +1063,7 @@ class AddView(UserPassesTestMixin, FormView):
# We can't just call request.build_absolute_uri in the template, because it would include query parameters
"absolute_add_path": self.request.build_absolute_uri(self.request.path),
"VERSION": VERSION,
- "FOOTER_INFO": get_config().FOOTER_INFO,
+ "FOOTER_INFO": request_config.FOOTER_INFO,
"required_search_plugin": required_search_plugin,
"plugin_dependency_map_json": json.dumps(plugin_dependency_map, sort_keys=True),
"persona_config_map_json": json.dumps(persona_config_map, sort_keys=True, default=str),
@@ -1820,6 +1821,7 @@ def live_progress_view(request):
for ar in sorted(snapshot_results, key=plugin_sort_key):
status = ar.status
+ process = ar.process_record
progress_value = 0
if status in (
ArchiveResult.StatusChoices.SUCCEEDED,
@@ -1829,8 +1831,8 @@ def live_progress_view(request):
):
progress_value = 100
elif status == ArchiveResult.StatusChoices.STARTED:
- started_at = ar.start_ts or (ar.process.started_at if ar.process_id and ar.process else None)
- timeout = ar.process.timeout if ar.process_id and ar.process else 120
+ started_at = ar.start_ts or (process.started_at if process else None)
+ timeout = process.timeout if process else 120
if started_at and timeout:
elapsed = max(0.0, (now - started_at).total_seconds())
progress_value = int(min(99, max(1, (elapsed / float(timeout)) * 100)))
@@ -1849,21 +1851,21 @@ def live_progress_view(request):
"hook_name": hook_name,
"phase": phase,
"status": status,
- "process_id": str(ar.process_id) if ar.process_id else None,
+ "process_id": str(process.id) if process else None,
"admin_url": f"/admin/core/archiveresult/{ar.id}/change/",
}
output_path = archiveresult_output_path(ar)
if output_path:
plugin_payload["output_path"] = output_path
plugin_payload["output_url"] = snapshot_view_url(snapshot, output_path)
- if status == ArchiveResult.StatusChoices.STARTED and ar.process_id and ar.process:
- plugin_payload["pid"] = ar.process.pid
+ if status == ArchiveResult.StatusChoices.STARTED and process:
+ plugin_payload["pid"] = process.pid
if status == ArchiveResult.StatusChoices.STARTED:
plugin_payload["progress"] = progress_value
- plugin_payload["timeout"] = ar.process.timeout if ar.process_id and ar.process else 120
+ plugin_payload["timeout"] = process.timeout if process else 120
plugin_payload["source"] = "archiveresult"
all_plugins.append(plugin_payload)
- seen_plugin_keys.add(str(ar.process_id) if ar.process_id else f"{ar.plugin}:{hook_name}")
+ seen_plugin_keys.add(str(process.id) if process else f"{ar.plugin}:{hook_name}")
for proc_payload, proc_started_at in process_records_by_snapshot.get(str(snapshot["id"]), []):
if not is_current_run_timestamp(proc_started_at, snapshot_run_started_at):
@@ -2158,15 +2160,7 @@ def live_config_list_view(request: HttpRequest, **kwargs) -> TableContext:
assert getattr(request.user, "is_superuser", False), "Must be a superuser to view configuration settings."
- # Get merged config that includes Machine.config overrides
- try:
- from archivebox.machine.models import Machine
-
- Machine.current()
- merged_config = get_config()
- except Exception:
- # Fallback if Machine model not available
- merged_config = get_config()
+ merged_config = get_config()
rows = {
"Section": [],
@@ -2231,7 +2225,6 @@ def live_config_value_view(request: HttpRequest, key: str, **kwargs) -> ItemCont
assert getattr(request.user, "is_superuser", False), "Must be a superuser to view configuration settings."
- # Get merged config
merged_config = get_config()
# Determine all sources for this config value
diff --git a/archivebox/tests/test_admin_links.py b/archivebox/tests/test_admin_links.py
index ef5e8999..dea81d52 100644
--- a/archivebox/tests/test_admin_links.py
+++ b/archivebox/tests/test_admin_links.py
@@ -96,6 +96,69 @@ def test_archiveresult_admin_links_plugin_and_process():
assert f"/admin/machine/process/{process.id}/change" in process_html
+def test_deleting_binary_and_process_records_preserves_results():
+ from archivebox.core.admin_archiveresults import ArchiveResultAdmin, build_abx_dl_replay_command, render_archiveresults_list
+ from archivebox.core.models import ArchiveResult
+ from archivebox.machine.admin import ProcessAdmin
+ from archivebox.machine.models import Binary, Process
+
+ snapshot = _create_snapshot()
+ machine = _create_machine()
+ binary = Binary.objects.create(
+ machine=machine,
+ name="wget",
+ abspath="/usr/bin/wget",
+ version="1.21.2",
+ binprovider="env",
+ binproviders="env",
+ status=Binary.StatusChoices.INSTALLED,
+ )
+ process = Process.objects.create(
+ machine=machine,
+ binary=binary,
+ process_type=Process.TypeChoices.HOOK,
+ pwd=str(snapshot.output_dir / "wget"),
+ cmd=["/tmp/on_Snapshot__06_wget.finite.bg.py", "--url=https://example.com"],
+ status=Process.StatusChoices.EXITED,
+ )
+ result = ArchiveResult.objects.create(
+ snapshot=snapshot,
+ plugin="wget",
+ hook_name="on_Snapshot__06_wget.finite.bg.py",
+ process=process,
+ status=ArchiveResult.StatusChoices.SUCCEEDED,
+ )
+
+ binary.delete()
+ process.refresh_from_db()
+ assert process.binary_id is None
+ assert process.cmd_version == ""
+ assert process.bin_abspath == ""
+ assert "binary_id" not in process.to_json()
+ assert ProcessAdmin(Process, AdminSite()).binary_link(process) == "-"
+
+ process.delete()
+ result.refresh_from_db()
+ assert result.process_id is None
+ assert ArchiveResult.objects.filter(id=result.id).exists()
+ assert result.pwd == str(result.output_dir)
+ assert result.cmd == []
+ assert result.cmd_version == ""
+ assert result.binary is None
+ assert result.iface is None
+ assert result.machine is None
+ assert result.timeout == 120
+ result_json = result.to_json()
+ assert result_json["pwd"] == str(result.output_dir)
+ assert "process_id" not in result_json
+
+ admin = ArchiveResultAdmin(ArchiveResult, AdminSite())
+ assert admin.process_link(result) == "-"
+ assert admin.machine_link(result) == "-"
+ assert "cd " in build_abx_dl_replay_command(result)
+ assert "wget" in render_archiveresults_list(ArchiveResult.objects.filter(id=result.id))
+
+
def test_snapshot_admin_zip_links():
from archivebox.core.admin_snapshots import SnapshotAdmin
from archivebox.core.models import Snapshot
diff --git a/archivebox/tests/test_persona_runtime.py b/archivebox/tests/test_persona_runtime.py
index d1a4e3d3..c1efb740 100644
--- a/archivebox/tests/test_persona_runtime.py
+++ b/archivebox/tests/test_persona_runtime.py
@@ -217,3 +217,91 @@ def test_get_config_raises_for_missing_persona_id(initialized_archive):
payload = json.loads(stdout.strip().splitlines()[-1])
assert payload["raised"] is True
assert "references missing Persona" in payload["message"]
+
+
+def test_get_config_resolves_parent_scopes_when_only_archiveresult_is_passed(initialized_archive):
+ script = textwrap.dedent(
+ """
+ import json
+ import os
+
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'archivebox.core.settings')
+ os.environ['TIMEOUT'] = '22'
+ os.environ['CHROME_BINARY'] = 'env-chrome'
+
+ import django
+ django.setup()
+
+ from archivebox.config import CONSTANTS
+ from archivebox.config.common import get_config
+ from archivebox.core.models import ArchiveResult, Snapshot
+ from archivebox.crawls.models import Crawl
+ from archivebox.machine.models import Machine
+ from archivebox.personas.models import Persona
+
+ CONSTANTS.CONFIG_FILE.write_text('[ARCHIVING_CONFIG]\\nTIMEOUT=11\\nCHROME_BINARY=file-chrome\\n')
+
+ machine = Machine.current()
+ machine.config = {'CHROME_BINARY': 'machine-chrome'}
+ machine.save(update_fields=['config'])
+
+ persona = Persona.objects.create(
+ name='StackPersona',
+ config={'TIMEOUT': 33, 'CHROME_BINARY': 'persona-chrome'},
+ )
+ persona.ensure_dirs()
+ crawl = Crawl.objects.create(
+ urls='https://example.com',
+ persona_id=persona.id,
+ config={'TIMEOUT': 44, 'CHROME_BINARY': 'crawl-chrome'},
+ )
+ snapshot = Snapshot.objects.create(
+ url='https://example.com',
+ crawl=crawl,
+ config={'TIMEOUT': 55, 'CHROME_BINARY': 'snapshot-chrome'},
+ )
+ result = ArchiveResult.objects.create(
+ snapshot=snapshot,
+ plugin='title',
+ config={'TIMEOUT': 66, 'CHROME_BINARY': 'archiveresult-chrome'},
+ )
+
+ env_config = get_config(include_machine=False)
+ machine_config = get_config(machine=machine)
+ persona_config = get_config(persona=persona)
+ crawl_config = get_config(crawl=crawl)
+ snapshot_config = get_config(snapshot=snapshot)
+ result_config = get_config(archiveresult=result)
+ override_config = get_config(archiveresult=result, overrides={'TIMEOUT': 77, 'CHROME_BINARY': 'override-chrome'})
+
+ print(json.dumps({
+ 'env': [env_config.TIMEOUT, env_config.CHROME_BINARY],
+ 'machine': [machine_config.TIMEOUT, machine_config.CHROME_BINARY],
+ 'persona': [persona_config.TIMEOUT, persona_config.CHROME_BINARY],
+ 'crawl': [crawl_config.TIMEOUT, crawl_config.CHROME_BINARY],
+ 'snapshot': [snapshot_config.TIMEOUT, snapshot_config.CHROME_BINARY],
+ 'archiveresult': [result_config.TIMEOUT, result_config.CHROME_BINARY],
+ 'override': [override_config.TIMEOUT, override_config.CHROME_BINARY],
+ 'snap_dir': str(result_config.SNAP_DIR),
+ 'expected_snap_dir': str(snapshot.output_dir),
+ 'crawl_dir': str(result_config.CRAWL_DIR),
+ 'expected_crawl_dir': str(crawl.output_dir),
+ 'active_persona': result_config.ACTIVE_PERSONA,
+ }, default=str))
+ """,
+ )
+
+ stdout, stderr, code = run_python_cwd(script, cwd=initialized_archive, timeout=60)
+ assert code == 0, stderr
+
+ payload = json.loads(stdout.strip().splitlines()[-1])
+ assert payload["env"] == [22, "env-chrome"]
+ assert payload["machine"] == [22, "machine-chrome"]
+ assert payload["persona"] == [33, "persona-chrome"]
+ assert payload["crawl"] == [44, "crawl-chrome"]
+ assert payload["snapshot"] == [55, "snapshot-chrome"]
+ assert payload["archiveresult"] == [66, "archiveresult-chrome"]
+ assert payload["override"] == [77, "override-chrome"]
+ assert payload["snap_dir"] == payload["expected_snap_dir"]
+ assert payload["crawl_dir"] == payload["expected_crawl_dir"]
+ assert payload["active_persona"] == "StackPersona"
diff --git a/etc/package.json b/etc/package.json
index 8f17346b..b116e7cc 100644
--- a/etc/package.json
+++ b/etc/package.json
@@ -1,6 +1,6 @@
{
"name": "archivebox",
- "version": "0.9.32rc25",
+ "version": "0.9.32rc26",
"repository": "github:ArchiveBox/ArchiveBox",
"license": "MIT",
"dependencies": {
diff --git a/pyproject.toml b/pyproject.toml
index 2f985ca5..3223a3c7 100755
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "archivebox"
-version = "0.9.32rc25"
+version = "0.9.32rc26"
requires-python = ">=3.13"
description = "Self-hosted internet archiving solution."
authors = [{name = "Nick Sweeting", email = "pyproject.toml@archivebox.io"}]
@@ -80,9 +80,9 @@ dependencies = [
### Extractor dependencies (optional binaries detected at runtime via shutil.which)
### Binary/Package Management
"abxbus==2.5.7", # EventBus API
- "abxpkg>=1.11.10", # for: detecting, versioning, and installing binaries via apt/brew/pip/npm
- "abx-plugins>=1.11.13", # shared ArchiveBox plugin package with Chrome/Puppeteer dependency wiring
- "abx-dl>=1.11.13", # shared ArchiveBox downloader package with blocking install preflight
+ "abxpkg>=1.11.11", # for: detecting, versioning, and installing binaries via apt/brew/pip/npm
+ "abx-plugins>=1.11.14", # shared ArchiveBox plugin package with Chrome/Puppeteer dependency wiring
+ "abx-dl>=1.11.14", # shared ArchiveBox downloader package with blocking install preflight
### UUID7 backport for Python <3.14
"uuid7>=0.1.0; python_version < '3.14'", # provides the uuid_extensions module on Python 3.13
]