Connected Is Not Writable
The first fifteen minutes of an AI coding setup almost never die on model quality, which still surprises people. They die on a quieter question: can this process write a file where you believe the repo lives? If that ans
The first fifteen minutes of an AI coding setup almost never die on model quality, which still surprises people. They die on a quieter question: can this process write a file where you believe the repo lives? If that answer is no, every later prompt becomes theater, because the agent acts on a disk you cannot see. I now refuse to send a first prompt until a canary file exists, because the operating system cannot fake that particular kind of proof.
Think of it like hiring movers after they sent a thumbs-up photo from the wrong driveway in the rain. The truck looks busy, the thread looks friendly, and your furniture is still getting wet on the curb. That is the developer experience of a green connected badge that never actually touched your working tree. Why do we trust a websocket handshake more than we trust pwd and a one-line write to disk?
Here is the friction I keep tracing through those opening minutes, and it remains painfully ordinary every time. You paste a repo path, the interface says ready, and the agent proposes edits like a confident intern on day one. Then nothing lands, or a file appears under /tmp, or the remote box writes bytes your laptop will never display. Was the failure the model, the prompt, a gitignore rule, or a uid that cannot write the directory you named?
Local sessions and free remote sessions fail in different costumes, which is why the first quarter hour feels haunted. On a laptop the agent may inherit a shell whose cwd is the last editor tab, not the repository root you described. On a throwaway server the hostname looks official while the mount is empty, read-only, or owned by a previous experiment. Either way the model narrates a patch that never existed, and you debug the wrong layer for the rest of the coffee.
The current fashion for in-browser agents and tool calling makes that confusion worse, because a successful HTTP call looks like work. Did the tool call write your file, or did it only serialize a pretty JSON argument that vanished into a log? I do not need another demo of an agent that never leaves the browser tab and still looks employed. I need evidence that the tab and the disk are the same character in the story.
The one fix that mattered for me was rude and small: prove write and execute before any chat token is streamed. I do not mean a vendor health dashboard, and I do not mean the first sentence that landing pages love to celebrate. I mean a canary the operating system itself will argue with, in the same shell the agent is about to use. If that canary cannot be created, listed, executed, and deleted, why would I care how pretty the agent interface looks?
The canary I actually run
This is the probe I paste into the shell the coding agent will share, before any prompt, every single time. Treat it as a proposed workflow encoded as a script, not as a benchmark of some unnamed host I cannot show you. Copy it, change the directory argument, and only trust the lines the kernel prints back at you. Do not paste it into a production volume unless you already know that volume is disposable.
#!/usr/bin/env bash
# canary-before-chat.sh β proposed probe, run in the agent's shell
set -euo pipefail
CANARY_DIR="${1:-.}"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
FILE="${CANARY_DIR%/}/.canary-${STAMP}-$$"
LOG="${CANARY_DIR%/}/.canary-${STAMP}-$$.log"
{
echo "host=$(hostname 2>/dev/null || echo unknown)"
echo "who=$(id -un 2>/dev/null || echo unknown) uid=$(id -u)"
echo "pwd=$(pwd -P)"
echo "target=$(cd "$CANARY_DIR" && pwd -P)"
command -v node && node -p process.version || echo "node=missing"
command -v git && git rev-parse --show-toplevel 2>/dev/null || echo "git-root=missing"
} | tee "$LOG"
echo "canary-ok $STAMP pid=$$" > "$FILE"
test -s "$FILE"
chmod u+x "$FILE" 2>/dev/null || true
cat > "${FILE}.run.js" <<'EOF'
const fs = require('fs');
const p = process.argv[2];
fs.accessSync(p, fs.constants.W_OK);
process.stdout.write('writable=' + p + '\n');
EOF
node "${FILE}.run.js" "$FILE"
rm -f "$FILE" "${FILE}.run.js" "$LOG"
echo "CANARY_PASS dir=${CANARY_DIR}"
Run it in the agent's environment and read the first four lines before you celebrate anything in the chat panel. The host line should name the machine you think you hired, not the laptop under your coffee cup. The target line should be the repository root you would defend in a code review. If those two facts are already wrong, the rest of the script is just documenting a well-lit mistake.
chmod +x canary-before-chat.sh
./canary-before-chat.sh .
./canary-before-chat.sh ./apps/web
If pwd and target disagree, you are about to review patches for a folder the agent will never open. If id -u is not the owner you expected, the next git apply will fail with a permission story that looks like a model hallucination. If node is missing on the box you planned for a JavaScript repo, why is the agent drafting Express middleware already? If the script dies on tee or the write, you found the real bug in under a minute, which was the point.
I keep a second, even smaller ritual for the moment the agent claims a patch already landed on disk. It is a cheap lie detector for the session, not a substitute for a real test suite, run from my terminal. Notice that I do not ask the model whether it wrote the file, because that question is how you get fan fiction. I ask find, which has no incentive to flatter me and no landing page to protect.
CANARY="dx-canary-$(date +%s)"
echo "expect file: $CANARY"
# After the agent runs, you run these two lines from the repo you can actually see.
find . -name "$CANARY" -maxdepth 3 -print
git status --short | head
If find prints nothing, the session was a radio play with a very confident narrator and no scenery. Do you really want to argue about React state after the filesystem has already told you the truth? I would rather fail the setup on purpose, close the pretty tab, and reopen the machine that can actually touch the repo. Chat history can wait; the canary cannot, because it is the only object both of you can point at.
When the agent prefers JavaScript, I use this tiny Node canary instead of arguing with bash quoting on a weird image. It uses an exclusive write flag so a leftover file from a crashed session cannot silently count as success. If wx throws, someone already left debris in the directory, which is information you wanted anyway. Labeled as a proposal, yes, but it is the proposal I actually want in the agent's path.
// canary-before-chat.mjs β proposed probe
// run: node canary-before-chat.mjs .
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
const dir = path.resolve(process.argv[2] || '.');
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
const file = path.join(dir, `.canary-${stamp}-${process.pid}.txt`);
const report = {
host: os.hostname(),
user: os.userInfo().username,
uid: os.userInfo().uid,
cwd: process.cwd(),
target: dir,
node: process.version,
};
fs.writeFileSync(file, JSON.stringify(report, null, 2) + '\n', { flag: 'wx' });
fs.accessSync(file, fs.constants.W_OK);
const git = spawnSync('git', ['rev-parse', '--show-toplevel'], {
cwd: dir,
encoding: 'utf8',
});
report.gitRoot = git.status === 0 ? git.stdout.trim() : 'missing';
fs.unlinkSync(file);
console.log('CANARY_PASS', report);
Reading the failure
Free remote boxes make this probe more important, not less, because network path and filesystem path get confused in a hurry. A laptop terminal can look like it is driving a cloud shell while you are still writing into ~/Downloads like a sleepwalker. A browser tab can look bound to your clone while the server cwd is /home/ubuntu with no repository and no write bit. The canary collapses that identity crisis into one file you can list, which is the only green light I trust in minute one.
When I want the remote half without standing up my own VM, I use MonkeyCode's free server option with its free model access. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I am not claiming a model name, a token quota, or an uptime number, because those details change and you should verify them yourself. The method stays portable: same user, same cwd, same write bit, and only then a prompt.
A compact way to interpret the probe, if you hate scrolling logs, is this decision table I keep above the keyboard during setup. It is not a scorecard for models, and it is not a vendor comparison dressed up as science. It is a map from a boring observation to the next boring action, which is how setups get unstuck. Read it once, then ignore any chat message that contradicts the row you are on.
| Probe observation | What it actually means | What I do next |
|---|---|---|
| host and pwd look like my laptop, but I intended a server | You never left the local shell | Open the remote session and rerun the canary there |
| target is not the git root | The agent will edit a cousin directory |
cd to the repo, or refuse the session |
| write fails with permission denied | Connected is not writable | Fix ownership or pick another workspace |
| node or git missing | The runtime story in chat is fiction | Install the toolchain or change hosts |
| canary writes, find cannot see it from my laptop | Two filesystems are sharing one conversation | Stop merging local git status with remote claims |
CANARY_PASS |
Disk agrees with the UI | Now the first prompt is allowed |
None of this measures intelligence, latency, or review quality, and I am glad it does not pretend to. A passing canary means the operating system agreed to be in the room with you. It does not mean the next refactor is wise, and it does not mean a free server is where your payroll app should live. Keep those harder questions for after the file exists, because the disk has to enter the conversation first.
Where this habit stops
Limitations sit right next to the habit, because a canary is a blunt instrument and it will not save a bad plan. It does not prove the model will follow your architecture, and it does not bless a shared box for secrets. It can pass on a directory you should never have exposed, which is worse than a loud permission error in minute one. It also assumes bash, id, and a Node binary, which is a biased default for a JavaScript-shaped workflow.
A slim image without Node will fail the script even when a Python-only workflow would have been perfectly fine. If that is your stack, delete the Node block and write the canary with printf and test -w until the disk argues back. Who should skip the whole ritual, even though it sounds cheap, righteous, and easy to automate? Skip it if you already live on a known workstation with one repo and a Makefile that smoke-tests the environment every morning.
Skip it if policy forbids throwing even a canary file onto a shared free server, which is a reasonable and adult rule. Skip it if you hoped this probe would become a production readiness gate, because it is not one and never will be. I still catch myself skipping the script when the interface is pretty and the first sentence arrives embarrassingly fast. That is the trap, and it is why I keep the canary rude enough to interrupt me.
Fifteen minutes of chatting about a patch that never touched disk is not research, and it is not evaluation either. Pretty tokens are a costume if the canary never existed, and I am tired of reviewing costumes. Would you accept a pull request from a teammate who never cloned the repo, just because their Slack message was eloquent? If you already have a throwaway shell open, run the write test first and let the conversation start only after the file exists.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.