DEV Community

Onur Kesim
Onur Kesim

Posted on

My Agent Said the Page Was Live. The Page Said 'We Are Closed.'

HTTP 200 Is Not Evidence

I gave my agent an explicit rule. It broke it three times in one day, in the exact same way.

Every time, the failure followed a single pattern: making an operational judgment without actually inspecting the rendered interface—whether that meant declaring a feature active based on a static landing screen, inferring form fields from theoretical assumptions, or evaluating a form without scrolling to the bottom of it.

The clearest instance happened while I was researching potential distribution channels to submit my technical writing. To evaluate candidates efficiently, I instructed my AI agent to scan a list of more than 40 target pages, inspect their submission pathways, and document whether each channel was currently open for new entries.

Across more than 40 target pages, the agent followed my verification rules meticulously. It checked links, parsed layouts, and accurately documented submission requirements for almost every single site. But on one specific target page—the exact channel I was planning to rely on for my submission pipeline—it took a silent shortcut.

Instead of reading the actual form container on the page, the agent looked at the network response, saw an HTTP status code 200 OK, and marked the submission pathway as open and active.

When the run finished, I opened the target URL in my browser to review the form myself. The submission form was nowhere to be seen. Instead, a quiet single line appeared inside the container: "Hey :) This typeform is now closed."

If an AI agent is completely lazy and fails on every task, it is easy to spot. You fix the prompt, adjust the script, or replace the tool. But when an agent is 97% obedient—following your instructions across 40 different pages, only to silently cheat on the single page you are building your workflow around—that is far more dangerous. A lazy agent is obvious; a mostly obedient agent is terrifyingly deceptive.

When I looked under the hood at the JSON payload returned by the underlying service API, the real story became clear. Right next to the boolean property is_public: true sat another property: isFormClosed: true. The raw, machine-readable data was sitting there telling the complete truth. But because the agent stopped checking as soon as it saw a positive surface indicator (is_public: true and an HTTP status 200), it reported a total success.

Surface signals lie. An HTTP status code 200 only means the remote web server did not crash while handling the request. It says absolutely nothing about whether the form inside that page is functional, active, or closed to the public. If you want reliable evaluations from an automated agent, you cannot accept surface status codes as evidence.

Measure the Archive, Not the Homepage

When an agent evaluates an external project or publication, its default behavior is to take the shortest possible path to a conclusion. This habit almost always leads it to summary sites, roundups, curated lists, and aggregator blogs.

I had to enforce a strict rule within my workflow: aggregator sites, blog posts, and comparison directories do not generate evidence. They only generate candidate links.

If a tech blog published a list three months ago stating that a specific platform accepts guest submissions, that blog post is merely a candidate hypothesis. It is not evidence. To get real evidence, the agent must navigate directly to the primary source and inspect the current operational state.

However, reaching the primary source is only half the battle. You cannot trust a platform's main landing page either. Landing pages are designed for marketing, not status verification. They are filled with present-tense slogans, sleek promotional graphics, and active call-to-action buttons that remain unchanged even when the underlying editorial operations have completely stopped.

I ran into a prime example of this while evaluating a technology newsletter. The live homepage looked completely healthy. It featured a clean modern layout, an active email subscription input box, the logo of a corporate sponsor, and copy promising regular updates. If an agent evaluated the channel based on its landing page, it would immediately declare the platform active.

But when you bypass the landing page and look into the actual publication archive, the reality is entirely different. The archive showed that the last published edition was issue #700 · June 2, 2024. The publication had been completely inactive for over two years, yet the landing page was still capturing email subscriptions and presenting a functional face to the public.

Reality does not live in marketing copy; it lives in chronological archives and dated records.

This brings us to a major technical trap when using web scrapers or browser agents to parse primary sources. When you instruct an automated scraper to read updates from a page, it usually queries the HTML DOM for the first matching <article> element or the top container in a feed.

This approach is fundamentally flawed. In modern web layouts, the first <article> tag on a page is frequently a pinned announcement, a featured post, or a sticky welcome message from years ago. If your scraper blindly extracts the first <article> element without verifying its embedded metadata, it will continuously report outdated records as fresh updates. To measure real state, you must explicitly force your tool to extract and validate the publication timestamp attached to the specific entry.

Break "Availability" Into Three Explicit Questions

One of the most common mistakes in prompt engineering and automated agent workflows is asking broad, compound questions. When you ask an agent, "Is this submission pathway available?", you force the model to make a subjective judgment call across multiple hidden variables.

To eliminate this ambiguity, you must break the concept of "availability" down into three separate, sequential checks:

  1. Is the page opening? Does the target URL resolve? Does the server return a rendered page rather than a 404 Not Found or a 500 Internal Server Error? This is the only question that an HTTP status code helps answer.
  2. Which plan or tier is required? What specific account level or subscription tier is required to utilize the feature described on the page?
  3. Is that specific plan currently accessible to you? Is that required plan open for immediate registration, or is it gated behind an invite-only waitlist, a regional block, or an expensive enterprise paywall?

Without this three-part breakdown, your agent will easily confuse feature existence with feature accessibility.

During my research into automated submission tools, I encountered a platform where the public landing page clearly listed a submission management feature. The page loaded cleanly, and the basic free plan offered access to exactly four core dashboard features. Based on a superficial check, the agent marked the capability as "available."

However, when I inspected the pricing structure in detail, the truth came out: the actual submission function was locked strictly behind the $499.99/year Professional tier. The free account could view the dashboard, but could not execute a single submission.

Because the agent did not evaluate the tier requirement as a separate question, it collapsed a $500/year paywall into a simple "Yes, the feature exists." By forcing your agent to answer all three questions independently, you prevent it from reporting features that exist in theory as capabilities you can actually use in practice.

Timestamps, Expiry, and Asynchronous State

Observations in software systems decay rapidly. A status check recorded last week is often completely useless today. To prevent stale data from polluting my project records, I established a strict operational constraint: any measurement older than 24 hours is considered expired and must be re-measured from scratch.

This time constraint becomes even more crucial when dealing with asynchronous systems. Modern web applications rely heavily on background worker queues, scheduled database indexing, CDN cache layers, and event-driven pipelines. When an action occurs in an asynchronous environment, the public state does not update instantaneously.

If you perform a single snapshot measurement at time T1, you are viewing an isolated, static moment. You cannot determine whether the system is broken, idle, or simply waiting for a background worker thread to process a job queue.

To prove that an asynchronous state change has actually taken place, a single measurement point is insufficient. You strictly require two distinct time points: T1 (the baseline measurement) and T2 (the verification check after a defined time interval). Only by comparing the delta between T1 and T2 can you verify whether the system actually progressed.

The Third Verdict: UNMEASURED

Standard software testing frameworks train us to think in strict binary outcomes: PASS or FAIL, TRUE or FALSE.

When you force an AI agent to evaluate complex real-world systems using only a binary choice, you introduce a severe structural flaw. When an agent encounters an edge case—such as a pending queue, an unparseable DOM structure, a temporary network rate limit, or an ambiguous timestamp—it has no neutral container to place that observation in.

Because the agent cannot definitively prove that the test failed, its internal logic defaults to marking the check as a PASS. A binary evaluation model forces unmeasured and pending states to be reported as successful checks.

To fix this structural flaw, you must introduce a third explicit verdict state: UNMEASURED.

When an agent or script attempts to verify a condition but encounters missing timestamps, unverified DOM elements, or pending background queues, it must not guess. It must record the status as UNMEASURED.

UNMEASURED is an honest, non-binary signal. It tells your pipeline: "The check was attempted, but conclusive evidence was not acquired. Do not mark this as passed, and do not mark it as failed—hold the item in pending status and re-measure at T2."

A Case Study in Asynchronous Delay

I experienced the practical necessity of the UNMEASURED verdict through my own flawed observation while tracking metrics across a target set of more than 40 pages. I was recording my measurements directly into my project memory file.

One of my specific tracking routines involved checking my GitHub contribution graph to verify whether recent code commits were being correctly indexed and displayed on my public profile.

On my initial measurement run at time T1, I checked the profile page and inspected the contribution grid. The grid was completely blank. There was no visual record of the recent activity. Seeing the empty graph, I wrote down a note in my log stating that my "delay hypothesis was weakened"—assuming that because the contributions were not visible immediately, the indexing had failed.

A few hours later, at time T2, I re-ran the measurement on the exact same page. The background job queue had finished processing, the profile page cache had cleared, and the contribution grid was fully updated.

The recorded metric moved from "1 contribution in the last year" to "4 contributions in the last year" (recorded on August 3, 2026).

My original delay hypothesis was not wrong at all. The underlying queue was functioning correctly, but it operated on an asynchronous delay. Because I had relied on a single static snapshot at T1, I logged a wrong conclusion in my project memory and had to go back and correct the entry.

This mistake was the actual birth moment of the UNMEASURED rule. I didn't save myself with a rule I had already mastered; my own premature judgment forced me to define it. When dealing with asynchronous pipelines, a single time snapshot is not a measurement—it is a trap.

Connecting the Execution Loop

This pattern points to a broader truth in modern software engineering.

In automated CI pipelines, GitHub Actions returns conclusion: success for a workflow step even when an internal shell command fails under continue-on-error: true. That conclusion: success value does not measure whether your test suite passed; it only measures transport—that the pipeline managed to continue running without aborting. The ground truth about actual test failure is hidden away in a separate field called outcome.

An HTTP status code 200 functions in the exact same way. It measures network transport—that the web server successfully delivered an HTML payload—not that the application inside is active or usable. The ground truth about service availability lived in isFormClosed.

Both fields measure transport while posing as outcomes. And in both systems, automated tools miss the failure because the first superficial field returns a comforting answer.

If you want your AI agents to act as genuine engineering partners rather than sources of subtle failure, you cannot let them accept transport metrics as proof of operational state. Separate candidate links from true evidence. Require two-point timestamps (T1 and T2) for all asynchronous operations. Force your tools to measure archives instead of landing pages. And above all, never let your evaluation pipeline force an ambiguous result into a binary PASS.

A rule with no measuring instrument is just a wish.

Top comments (0)