diff --git a/tests/e2e/test_plugin_chain_hostile_env.py b/tests/e2e/test_plugin_chain_hostile_env.py
new file mode 100644
index 0000000..2d0a9f0
--- /dev/null
+++ b/tests/e2e/test_plugin_chain_hostile_env.py
@@ -0,0 +1,381 @@
+"""
+🔴 E2E Plugin Chain Integration Tests
+========================================
+
+These tests exercise the FULL plugin chain (PluginRegistry.execute_all) against
+hostile environments that the bot encounters in production:
+ 1. System permission dialogs (Android PermissionController)
+ 2. Foreign app overlays (browser, wrong app in foreground)
+ 3. Reel creation flows (camera, audio recording prompts)
+
+WHY THESE TESTS EXIST:
+ Production run 2026-04-29 23:01 revealed that:
+ - The SAE correctly detects OBSTACLE_SYSTEM for permission dialogs
+ - The obstacle_guard plugin IGNORED OBSTACLE_SYSTEM (only handled OBSTACLE_MODAL)
+ - All downstream plugins (resonance, dwell, like, follow) fired against a
+ non-Instagram permission dialog
+ - The VLM hallucinated box [4] (icon) as "post username" and "media content"
+ - The bot got completely stuck in an infinite loop
+
+ NONE of the existing 81 E2E tests caught this because:
+ - They test each plugin in ISOLATION (not as a chain)
+ - They NEVER call PluginRegistry.execute_all() against hostile environments
+ - The conftest registers all plugins but no test actually exercises the chain
+
+ These tests fix that gap permanently.
+"""
+
+import argparse
+import os
+
+import pytest
+
+from GramAddict.core.behaviors import BehaviorContext, BehaviorResult, PluginRegistry
+from GramAddict.core.situational_awareness import SituationType
+
+
+# ═══════════════════════════════════════════════════════
+# Hostile XML Fixtures (inline for speed, no file I/O)
+# ═══════════════════════════════════════════════════════
+
+PERMISSION_DIALOG_XML = """
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+FOREIGN_APP_CHROME_XML = """
+
+
+
+
+
+
+"""
+
+NORMAL_INSTAGRAM_XML = """
+
+
+
+
+
+
+"""
+
+
+# ═══════════════════════════════════════════════════════
+# Realistic Device Stub
+# ═══════════════════════════════════════════════════════
+
+
+class ChainTestDevice:
+ """Deterministic device for chain testing. Returns XML sequence."""
+
+ def __init__(self, xml_sequence):
+ self._xml_sequence = list(xml_sequence)
+ self._dump_index = 0
+ self.pressed_keys = []
+ self.clicks = []
+ self.app_id = "com.instagram.android"
+
+ class _V2:
+ info = {"screenOn": True, "sdkInt": 30, "displaySizeDpX": 400, "displayWidth": 1080}
+ settings = {}
+ touch = type("T", (), {"down": lambda s, x, y: None, "up": lambda s, x, y: None})()
+
+ def dump_hierarchy(self, compressed=False):
+ return ""
+
+ def app_current(self):
+ return {"package": "com.instagram.android"}
+
+ def screenshot(self):
+ return None
+
+ def shell(self, cmd):
+ pass
+
+ self.deviceV2 = _V2()
+
+ def dump_hierarchy(self):
+ if self._dump_index < len(self._xml_sequence):
+ xml = self._xml_sequence[self._dump_index]
+ self._dump_index += 1
+ return xml
+ return self._xml_sequence[-1]
+
+ def press(self, key):
+ self.pressed_keys.append(key)
+
+ def click(self, x, y):
+ self.clicks.append((x, y))
+
+ def swipe(self, sx, sy, ex, ey, **kwargs):
+ pass
+
+ def app_start(self, pkg, use_monkey=False):
+ pass
+
+ def app_stop(self, pkg):
+ pass
+
+ def unlock(self):
+ pass
+
+ def get_screenshot_b64(self):
+ return None
+
+
+class ChainTestConfig:
+ def __init__(self):
+ self.args = argparse.Namespace(
+ username="testuser",
+ device="emulator-5554",
+ app_id="com.instagram.android",
+ debug=True,
+ interact_percentage=100,
+ visual_vibe_check_percentage=0,
+ persona_interests="travel, nature",
+ target_audience="travel, nature",
+ speed_multiplier=1.0,
+ likes_count="1",
+ likes_percentage=100,
+ follow_percentage=100,
+ comment_percentage=0,
+ carousel_percentage=0,
+ carousel_count="1",
+ stories_count="0",
+ stories_percentage=0,
+ disable_ai_messaging=True,
+ scrape_profiles=False,
+ dry_run_comments=True,
+ )
+ self.config = {
+ "plugins": {
+ "likes": {"count": "1", "percentage": 100},
+ "follow": {"percentage": 100},
+ "comment": {"percentage": 0, "dry_run": True},
+ "stories": {"count": "0", "percentage": 0},
+ "resonance_evaluator": {"visual_vibe_check_percentage": 0},
+ "carousel_browsing": {"percentage": 0, "count": "1"},
+ }
+ }
+ self.username = "testuser"
+
+ def get_plugin_config(self, name):
+ return self.config.get("plugins", {}).get(name, {})
+
+
+class ChainTestSession:
+ def __init__(self):
+ self.totalLikes = 0
+ self.totalFollowed = 0
+ self.totalComments = 0
+ self.totalGetProfile = 0
+ self.totalReposted = 0
+ self.totalStoriesWatched = 0
+ self.totalScraped = 0
+ self.job_target = "test_chain"
+ self.totalInteractions = {}
+
+ def check_limit(self, *args, **kwargs):
+ return False
+
+
+# ═══════════════════════════════════════════════════════
+# THE ACTUAL CHAIN INTEGRATION TESTS
+# ═══════════════════════════════════════════════════════
+
+
+class TestPluginChainOnSystemDialog:
+ """
+ THE BIG LIE: Before this test existed, no E2E test exercised the full
+ plugin chain against a system permission dialog. The SAE perception test
+ verified detection, but obstacle_guard silently ignored it.
+
+ This test proves the chain TERMINATES on hostile environments.
+ """
+
+ def test_chain_terminates_on_permission_dialog(self, monkeypatch, setup_e2e_plugin_registry):
+ """
+ When PluginRegistry.execute_all() runs against a permission dialog,
+ the obstacle_guard (priority 95) MUST fire and terminate the chain.
+ No downstream plugin (resonance, like, follow) may execute.
+ """
+ device = ChainTestDevice([PERMISSION_DIALOG_XML, NORMAL_INSTAGRAM_XML])
+ registry = setup_e2e_plugin_registry
+
+ # Monkeypatch SAE to return OBSTACLE_SYSTEM
+ class FakeSAE:
+ @classmethod
+ def get_instance(cls, device=None):
+ return cls()
+
+ def perceive(self, xml):
+ if "permissioncontroller" in xml:
+ return SituationType.OBSTACLE_SYSTEM
+ return SituationType.NORMAL
+
+ monkeypatch.setattr(
+ "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine",
+ FakeSAE,
+ )
+
+ ctx = BehaviorContext(
+ device=device,
+ configs=ChainTestConfig(),
+ session_state=ChainTestSession(),
+ cognitive_stack={},
+ shared_state={},
+ context_xml=PERMISSION_DIALOG_XML,
+ )
+
+ results = registry.execute_all(ctx)
+
+ # The chain MUST have terminated before any INTERACTION plugins fired
+ assert len(results) >= 1, "No plugin executed at all"
+
+ # Find the obstacle_guard result (should be the one that executed + should_skip)
+ guard_fired = any(r.executed and r.should_skip for r in results)
+ assert guard_fired, (
+ "obstacle_guard did not fire — no result has executed=True + should_skip=True. "
+ "This means the permission dialog was ignored and interaction plugins will run!"
+ )
+
+ # Critical: NO interaction plugin may have performed actual interactions
+ total_interactions = sum(r.interactions for r in results)
+ assert total_interactions == 0, (
+ f"{total_interactions} interaction(s) were performed against a permission dialog!"
+ )
+
+ # The device must have pressed BACK to dismiss the dialog
+ assert "back" in device.pressed_keys, (
+ "obstacle_guard did not press BACK to dismiss the system dialog"
+ )
+
+ def test_chain_terminates_on_foreign_app(self, monkeypatch, setup_e2e_plugin_registry):
+ """
+ When Chrome is in the foreground, the chain must terminate at obstacle_guard.
+ """
+ device = ChainTestDevice([FOREIGN_APP_CHROME_XML, NORMAL_INSTAGRAM_XML])
+ registry = setup_e2e_plugin_registry
+
+ class FakeSAE:
+ @classmethod
+ def get_instance(cls, device=None):
+ return cls()
+
+ def perceive(self, xml):
+ if "chrome" in xml:
+ return SituationType.OBSTACLE_FOREIGN_APP
+ return SituationType.NORMAL
+
+ monkeypatch.setattr(
+ "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine",
+ FakeSAE,
+ )
+
+ ctx = BehaviorContext(
+ device=device,
+ configs=ChainTestConfig(),
+ session_state=ChainTestSession(),
+ cognitive_stack={},
+ shared_state={},
+ context_xml=FOREIGN_APP_CHROME_XML,
+ )
+
+ results = registry.execute_all(ctx)
+
+ # obstacle_guard must have fired and terminated the chain
+ guard_fired = any(r.executed and r.should_skip for r in results)
+ assert guard_fired, (
+ "obstacle_guard did not fire on foreign app. "
+ "Downstream plugins are firing against a Chrome browser window!"
+ )
+
+ total_interactions = sum(r.interactions for r in results)
+ assert total_interactions == 0, (
+ f"{total_interactions} interaction(s) were performed against Chrome!"
+ )
+ assert "back" in device.pressed_keys
+
+
+class TestPluginChainOnNormalFeed:
+ """
+ Sanity check: On a NORMAL Instagram feed, the chain must run normally.
+ The obstacle_guard must NOT fire (executed=False), allowing downstream plugins.
+ """
+
+ def test_chain_passes_through_on_normal_instagram(self, monkeypatch, setup_e2e_plugin_registry):
+ device = ChainTestDevice([NORMAL_INSTAGRAM_XML])
+ registry = setup_e2e_plugin_registry
+
+ class FakeSAE:
+ @classmethod
+ def get_instance(cls, device=None):
+ return cls()
+
+ def perceive(self, xml):
+ return SituationType.NORMAL
+
+ monkeypatch.setattr(
+ "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine",
+ FakeSAE,
+ )
+
+ ctx = BehaviorContext(
+ device=device,
+ configs=ChainTestConfig(),
+ session_state=ChainTestSession(),
+ cognitive_stack={},
+ shared_state={},
+ context_xml=NORMAL_INSTAGRAM_XML,
+ )
+
+ results = registry.execute_all(ctx)
+
+ # Multiple plugins should have run (obstacle_guard passed through, then data extraction, etc.)
+ assert len(results) > 1, (
+ f"Only {len(results)} plugin(s) executed on a normal Instagram feed. "
+ f"The obstacle_guard may be incorrectly blocking the chain."
+ )
+
+ # obstacle_guard must have executed=False (no obstacle detected)
+ obstacle_results = [r for r in results if not r.executed or "back" not in device.pressed_keys]
+ assert "back" not in device.pressed_keys, (
+ "obstacle_guard pressed BACK on a normal Instagram screen — false positive!"
+ )