Build1 publisher3 min readPublished
A support-ticket agent handed back the production connection string it was given
One engineer's account of an MCP rejection that cascaded into a secrets leak and a loop that would not stop carries no incident numbers, but the config blocks and handler code it prints show where the failure boundary has to sit.
The Engineer · Build desk

What happened
- A dev.to writeup says the author's first production agent loop crashed the billing service after a Model Context Protocol rejection cascaded into an environment variable leak and a 70-line trace that never terminated.
- In a separate incident, a Docker Compose typo gave a ticket-summarising agent the production database connection string, and the model, told to provide complete diagnostic information, put it in its response.
- The article names four recurring rejection triggers: unsupported tool ordering, context overflow past the server's buffer, mid-session token expiry, and rate limits hit with no backoff in the controller.
- Its proposed remedy is a rejection handler that converts failures into recoverable state transitions, built as a small switch on the error code with a rethrow for anything it does not recognise.
- Three secret-handling patterns accompany it: scoped per-agent environments, credentials fetched dynamically against the agent's current role, and an output filter that redacts credential-shaped strings.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint A switch-based rejection handler is only as wide as the codes someone remembered to enumerate, so recoverability becomes a list you maintain against servers you do not control rather than a property of the loop.
- exposure A leak that travels out through the model's response is untouched by shrinking the environment, so any agent whose replies reach customers or downstream systems needs redaction on the output path before it is trusted with a credential of any grade.
- decision Granting an agent a service account forces a choice about which failure it is permitted to make: stop hard on an unrecognised rejection, or keep retrying and keep billing, and the source argues the second is the worse outcome.
Read the switch statement before the argument. The printed controller handles three codes: CONTEXT_OVERFLOW compresses context, RATE_LIMITED backs off exponentially, UNAUTHENTICATED refreshes credentials, and anything else throws RecoveryFailure [5]. The same piece names four situations where rejections turn up most often, and the first is the model calling tools in an order the server does not support [3]. That one has no case. It reaches the default branch and becomes an exception again [12]. Workable, as long as you know it: the boundary is exactly as wide as your enumeration of error codes, and it wants re-reading every time a server you do not own adds one.
The leak is the more instructive incident. The connection string sat in the process environment because of a Docker Compose typo, and the model printed it because it had been told to provide complete diagnostic information [7]. Nothing in that chain requires the model to misbehave. It did what the instruction said with what the runtime handed it.
The two Compose blocks show how narrow the recommended fix actually is. The first exposes DATABASE_URL, API_KEY and REDIS_URL; the second exposes a read-only database connection and the same REDIS_URL [8]. The delta is one master key dropped and one write credential downgraded to read [13]. That shrinks the blast radius without touching the leak path, which ran from environment to model context to response, and a read-only connection string pasted into a support summary is still a credential sitting in a ticket. Of the three secret-handling patterns proposed, only the output filter is on that path [14]. Dynamic credential injection moves the secret from the environment into process memory, where any tool return value can still carry it back into context [9].
Non-termination is the failure with the clearest budget implication. The article rates a 70-line trace that never ends as more dangerous than an immediate crash, because resources are consumed linearly while output value stays at zero [10]. Look at what the recovery branches do: each returns a state transition back into the agent loop [15]. A controller that can always recover is a controller that can always continue. What the printed sample lacks is a ceiling above the handler, counted in steps or tokens, that halts the loop regardless of how recoverable each individual rejection was [15].
What this evidence will not support is a frequency claim. There are no dates and no failure rates before or after, and the only quantity in the account is the length of one trace [2]. It is one engineer's writeup, published on dev.to from tamiz.pro, and the text stops mid-sentence in the loop section [11]. The anecdotes are illustration; the config listings and the handler code are what a reader can check against a deployment they already run. On that evidence the plumbing reading holds in one direction at least: neither incident would have been repaired by a better prompt [1], and the ticket agent leaked because an instruction worked.
What to watch
- Whether the full post's loop section, truncated in the text supplied, names a concrete stopping rule such as a step cap or a progress check.
- Whether MCP servers converge on a documented, stable set of rejection codes, which is what makes a switch-based failure boundary maintainable rather than a guess.
- Any postmortem with numbers attached: retry counts, token spend per stuck loop, or time to detect a credential in agent output.