DEV Community

Dakota Huang
Dakota Huang

Posted on

Retry the Request, Not the Prompt: An Error Taxonomy for Free Coding Models

Most retry logic retries the prompt. Effective triage retries only the layer that failed.

A free coding model endpoint fails in layers:

  • the request may be invalid
  • the transport may time out
  • the server may be rate limited
  • the model may truncate or return output that breaks your parser

If your client retries all of these the same way, you burn free quota on deterministic bugs.

The fix is small: classify the failure, then retry or repair.

Why blind retry fails

A typical client looks like this:

try:
    response = call_free_model(prompt)
except Exception:
    time.sleep(2)
    response = call_free_model(prompt)
Enter fullscreen mode Exit fullscreen mode

This retries a malformed prompt. It retries a max-token truncation. It treats every failure as transient.

Free hosted endpoints make this worse:

  • 429 means rate limit, not model problem
  • 502/503 mean overload, not model problem
  • 200 with truncated text and done: false means the max token limit was hit
  • 400 with a validation body means the request is bad and will never succeed

Retrying a 400 is not persistence. It is a bug.

Classify before you retry

Capture four fields on every failed call:

  • HTTP status
  • exception type
  • response body
  • whether the model marked completion

Use a tiny classifier.

import time
import requests

def valid_output(body: dict) -> bool:
    text = body.get('text', '')
    return bool(text.strip())

def classify_failure(status, error, body, token_limit):
    if status == 400:
        return {
            'class': 'request',
            'action': 'fix_request',
            'retry': False,
        }

    if status in (429, 502, 503, 504) or isinstance(error, requests.Timeout):
        return {
            'class': 'transport_or_quota',
            'action': 'backoff_once',
            'retry': True,
        }

    if status == 200:
        text = body.get('text', '')
        done = body.get('done', True)
        if len(text) < token_limit * 0.8 and not done:
            return {
                'class': 'model_truncation',
                'action': 'lower_prompt_or_raise_limit',
                'retry': False,
            }

        if not valid_output(body):
            return {
                'class': 'model_output',
                'action': 'reparse_or_rerun',
                'retry': False,
            }

    return {
        'class': 'unknown',
        'action': 'log_and_stop',
        'retry': False,
    }
Enter fullscreen mode Exit fullscreen mode

Append each result to a dict log. That gives you a failure history instead of a wall of exceptions.

{'ts': '2026-08-12T09:14:02Z', 'class': 'transport_or_quota', 'status': 429, 'retry': True}
{'ts': '2026-08-12T09:14:05Z', 'class': 'request', 'status': 400, 'retry': False, 'detail': 'unknown field'}
{'ts': '2026-08-12T09:14:11Z', 'class': 'model_truncation', 'status': 200, 'retry': False, 'token_limit': 512}
Enter fullscreen mode Exit fullscreen mode

One look at this log tells you which fixes are worth doing.

Use a second endpoint to split transport from model

The classifier cannot tell some failures apart by itself.

A 200 with odd output may be:

  • model quality
  • a hidden prompt limit
  • a server-specific parser quirk

The cheapest split is to replay the same prompt against a second endpoint and compare the class, not the score.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

I use MonkeyCode's free model access and free server option as that second lane. Those two availability claims are operator-supplied, so I verify them like any endpoint: status codes, latency, and token behavior before relying on the comparison.

The replay is a narrow diagnostic, not a benchmark:

def compare_triage(prompt, primary, secondary):
    results = {}
    for name, endpoint in (('primary', primary), ('secondary', secondary)):
        status, error, body = call_endpoint(endpoint, prompt)
        results[name] = classify_failure(status, error, body, endpoint['token_limit'])
    return results
Enter fullscreen mode Exit fullscreen mode

Use the result as a decision table:

Primary class Secondary class Meaning Action
request request The prompt is invalid Fix the request
transport_or_quota 200 valid Primary host is overloaded Retry primary or switch host
200 invalid 200 valid Primary parser limit differs Adjust token or parser
200 invalid 200 invalid Model quality issue Change prompt, not endpoint

The point is not to chase the best model. It is to stop retrying failures that will not change.

Limits

  • Status codes are not always reliable. Some endpoints return 200 for failed generations.
  • The token threshold is heuristic. A short valid answer can look like truncation.
  • Two endpoints can differ in model version without telling you. The comparison still has noise.
  • A free server can be rate limited too. Keep the replay budget small.
  • This taxonomy improves triage. It does not measure whether the generated code is correct.

Who should skip this

Skip the second-endpoint replay if:

  • You handle regulated or private prompt data. Route that to an approved environment.
  • You need hard latency SLAs. Backoff and diagnostics add milliseconds you may not have.
  • You expect guaranteed capacity. Free tiers change; treat them as variable.
  • You already have structured server logs. Add the classifier to your existing pipeline instead of running a new script.

Keep the classifier next to the client, not behind a dashboard. The value drops if the retry decision happens after you have already retried three times.

Run the checker on a sandbox prompt before pushing it into CI. Every classifier needs its own failure log.

Top comments (0)