It looked like a URL. You sent it. It doesn't exist — and never did. Why models produce link-shaped text when you ask them to share something, how to spot it in one second, and what actually has to happen for a real link to exist.
You asked for a link you could send someone. You got back something that looked exactly like one. You sent it — and the reply came back that it doesn't open.
It isn't broken. There is nothing at that address, and there never was.
It helps to see this from the model's side, because once you do it stops feeling like a malfunction.
A language model's job is to produce the most plausible continuation of what came before. You wrote something ending in "give me a shareable link". The most plausible continuation of that, as text, is a string that looks like a shareable link. So it writes one — correct protocol, believable domain, sensible-looking path, an ID with the right amount of randomness.
What did not happen anywhere in that sequence is a file being stored somewhere. Storing a file is not a kind of writing. It requires a capability — a connector, an integration, an upload tool — that is attached to the conversation separately, and that most conversations simply don't have.
So the response is not a lie in any interesting sense, and it isn't a bug either. You asked for text in the shape of a link and received text in the shape of a link. The gap is between what the shape implies and what actually took place.
This is also why the behaviour feels so inconsistent. Sometimes the model says plainly that it can't host files. Sometimes it produces <private_url> and leaves the brackets in, which reads almost like it's showing its working. Sometimes it produces something flawless. All three are the same underlying situation resolving differently — there is no publishing capability present, and the model is picking among ways to continue the sentence.
The visible stand-in. <private_url>, <your-link>, [insert link here], {{share_url}}. Angle brackets and curly braces never appear in a working web address, so these cost you nothing — you spot them instantly. If anything they're the honest version: the model is marking the slot where an address would go.
The generic example. https://example.com/your-file.html, https://yourdomain.com/report. example.com is a reserved domain that exists specifically to be used in documentation, and it will never host your file. Still easy to catch, if you read the address rather than glancing at it.
The plausible invention. A real-sounding service, a realistic path, an ID that looks exactly like the random strings real services generate. This is the one that costs you something, because there is no visual tell. It passes every inspection you'd think to do. You send it to a client, and the mistake surfaces in their reply rather than in your review.
That third case is worth being unembarrassed about. Producing convincing text is the thing these systems are best at, and a URL is very short text. Expecting yourself to catch a good fake by looking at it is expecting to win at the model's strongest game.
Open it in a browser tab. Not the preview in the chat window — a fresh tab.
Two seconds, no judgement call, catches all three shapes including the plausible one. It's the whole method.
If you want to confirm the diagnosis rather than just the symptom, ask the model directly: what tools do you have available for publishing or hosting? If nothing comes back, it had no way to store your file, which settles it. That same question is the first step when a publishing tool is attached and still nothing happens — the four layers that can break a publish are worth knowing apart, because they look identical from the chat window.
For an address to work, some service has to have received your file's bytes and stored them somewhere reachable. That's it. There's no third option and no prompt that substitutes for it.
Two routes get you there.
Attach a publishing capability to the conversation. A connector or integration gives the model an actual tool to call. Ask it to publish, it calls the tool, the service stores the file and returns an address, and the model reports back a URL it genuinely created rather than composed. Set up once, then it's the fast path. The difference is visible in the response: a real publish usually mentions the step happening, not just the result.
Or publish it yourself. Save the HTML the model wrote to a file, upload it somewhere that hands back a URL, send that. No setup, works from any assistant, and has the side benefit that you're holding the file — which matters more than it sounds, because a page that lives only inside a chat transcript is a page you can lose.
For a single self-contained HTML file, uploading takes about as long as reading this paragraph. If the page has a backend, a login, or a database behind it, you need a real application host — Vercel and Netlify are the right shape for that, and no amount of drag-and-drop hosting will substitute.
Once you've ruled out the placeholder, there are two nearby situations that produce the same complaint from your recipient and have completely different causes.
The link is real but it's tied to the assistant. Many AI tools do produce genuine share links for the things you build in them — and those links often carry assumptions about who's opening them. Sometimes the recipient is asked to sign in. Sometimes it opens blank because the session that generated it has ended. That's a real address behaving as designed, not an invention, and it's worth knowing why an AI tool's share link fails the person you sent it to tool by tool.
The link is real and shares more than you meant. A genuine share link from a chat assistant may hand over the whole conversation rather than just the artifact you had in mind. That's not a placeholder problem at all — it's a scope problem, and what a Grok share link actually shares walks through what one of them puts on the other side of the URL.
Both of those are the good case in a sense: an address exists. The placeholder case is the one where the address was never anything but a sentence.
miinideck turns a self-contained HTML file into a real, working link — one you can open yourself before you send it. Private and no-index by default, with an optional password and expiry. No account needed to try one.
You can't prompt a model into having a capability it lacks. You can prompt it into telling you clearly that it lacks one, which turns a silent invention into an obvious answer:
Before giving me any URL, state whether you actually published anything and which tool you used. If you have no publishing tool available, say so plainly and give me the HTML file instead — do not produce an example or placeholder address.
Two things make this work. Asking it to name the tool means an invented answer has to invent a tool call as well, which is a much less natural continuation than inventing a string. And offering the alternative — give me the file instead — removes the pressure to satisfy the shape of the request, because you've supplied a different way to be helpful.
It isn't a guarantee. Nothing at the prompt layer is. But it converts most of the plausible-invention cases into a clear "I can't do that", which is all you actually needed.
A model that hands you a URL has not necessarily put anything anywhere. The address is text until some service has your file, and text is free to look however convincing it needs to look.
So: open it yourself, in a real tab, before it goes to anyone else. Everything else in this page is explanation for why that one habit is the whole defence.
The agent handed you a URL and said it's live. It probably is. What it didn't tell you is who can open it, how long it lasts, whether the address is guessable, and what happens to it when you revise — four defaults chosen for you, none of them announced.
ChatGPT Sites hosts what you build and gives you access controls — owner only, named workspace members, the whole workspace, or the public internet. There's no rung for one outside client. Here's the shape of that gap and the route round it.
The steps take about ten seconds. The cost is less obvious: unpublishing permanently retires that artifact's ability to be published, and deletes its stored data. Here's how to do it, what it takes with it, and what to reach for when access only needs to end for now.
miinideck turns a single HTML file into an unguessable link with optional password and expiry. Default-private, never indexed.