<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: labyrinthlab</title>
    <description>The latest articles on DEV Community by labyrinthlab (@labyrinthlab).</description>
    <link>https://dev.to/labyrinthlab</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4171171%2F42836266-d88b-4292-98ec-5b56498d0955.png</url>
      <title>DEV Community: labyrinthlab</title>
      <link>https://dev.to/labyrinthlab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/labyrinthlab"/>
    <language>en</language>
    <item>
      <title>verizon connect work to jobber or housecall pro: opening your attachment zips and importing your jobs</title>
      <dc:creator>labyrinthlab</dc:creator>
      <pubDate>Wed, 28 Oct 2026 13:40:12 +0000</pubDate>
      <link>https://dev.to/labyrinthlab/verizon-connect-work-to-jobber-or-housecall-pro-opening-your-attachment-zips-and-importing-your-co1</link>
      <guid>https://dev.to/labyrinthlab/verizon-connect-work-to-jobber-or-housecall-pro-opening-your-attachment-zips-and-importing-your-co1</guid>
      <description>&lt;p&gt;this is the follow up to &lt;a href="https://dev.to/labyrinthlab/leaving-verizon-connect-work-how-to-move-your-clients-and-job-history-into-your-next-tool-1l1c"&gt;leaving verizon connect work? how to move your clients and job history into your next tool&lt;/a&gt;. that one covers the export basics. this one starts after the zips land: what's in them, and how much jobber or housecall pro can actually take.&lt;/p&gt;

&lt;h2&gt;
  
  
  the dates to plan around
&lt;/h2&gt;

&lt;p&gt;from verizon's &lt;a href="https://support.work.fleetmatics.com/hc/en-us/articles/51620051793683-Final-Transition-Deadlines-Data-Export-Cutoffs" rel="noopener noreferrer"&gt;final transition deadlines &amp;amp; data export cutoffs&lt;/a&gt; article (updated 22 oct 2026):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;31 dec 2026&lt;/strong&gt;: end of active service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 jan 2027&lt;/strong&gt;: platform ui restriction. admin logins go straight to the data migration tool and the mobile app stops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;28 feb 2027&lt;/strong&gt;: last day to start a new export. verizon says large exports "can take over a week to process".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;31 mar 2027&lt;/strong&gt;: last day to download finished zip files. after that verizon says job logs, signatures and media attachments are "irretrievably purged".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;verizon's &lt;a href="https://support.work.fleetmatics.com/hc/en-us/articles/51911728471059-Final-Billing-Cycles" rel="noopener noreferrer"&gt;final billing cycles&lt;/a&gt; page says that after 31 dec 2026 "the user interface will be locked". so plan for 31 dec, and treat the later dates as a safety net for exports only.&lt;/p&gt;

&lt;h2&gt;
  
  
  what's in the attachment zips
&lt;/h2&gt;

&lt;p&gt;honest caveat: i don't have a real work export, and verizon doesn't publish a sample, so no file names here. this is what verizon's &lt;a href="https://support.work.fleetmatics.com/hc/en-us/articles/53055861646739-Work-Data-Export-Migration-Tool" rel="noopener noreferrer"&gt;migration tool guide&lt;/a&gt; (updated 21 oct 2026) says:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;date range exports come as zip files &lt;strong&gt;grouped by month&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;"jobs with attachments" gives you job and task data plus attached files like photos and documents. "attachments will be in individual folders numbered for each associated job."&lt;/li&gt;
&lt;li&gt;you also need the plain jobs export in csv. verizon calls it "your master key": "column a in your csv contains the unique 'job id,' which corresponds exactly to the numbered folders in your downloaded zip file."&lt;/li&gt;
&lt;li&gt;the faq on the same page says attachment file names "correlate directly" with those job ids.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;so the workflow is: unzip a month, open that month's jobs csv, and use column a to go from a numbered folder to a client, address and date. if your files look different, trust your files.&lt;/p&gt;

&lt;p&gt;same goes for column names. verizon's guide only names column a, so any work column names you see below or in a mapping are a starting point, not gospel. take the real ones from your own export. the map in the guide marks them "check your export" for exactly that reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  opening them without making a mess
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;unzip each month into its own folder named for the month, like &lt;code&gt;2024-03&lt;/code&gt;. keep the original zips untouched somewhere else.&lt;/li&gt;
&lt;li&gt;don't double click the csv. use your spreadsheet's import option and set id, phone and zip columns to text, so job ids and leading zeros survive.&lt;/li&gt;
&lt;li&gt;spot check: pick a job you remember, read its column a, open that folder. if the photos match, the month is good.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;if you want to keep a readable copy of the old jobs before you import, there's a &lt;a href="https://job-archive-viewer.pages.dev/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=vcw-to-jobber-hcp" rel="noopener noreferrer"&gt;free viewer&lt;/a&gt; that opens the zip, csv and json files from a work export as a browsable archive. the files stay in your browser, nothing gets uploaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  what jobber can import
&lt;/h2&gt;

&lt;p&gt;i read these on 23 oct 2026.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;clients&lt;/strong&gt; (&lt;a href="https://help.getjobber.com/en/articles/import-clients/" rel="noopener noreferrer"&gt;import clients&lt;/a&gt;, updated 6 aug 2026): csv, tsv or psv. admin only, and it won't run in an incognito or private window. each file must be under 2.5mb, (about 5000 rows), so split bigger lists. it takes names, company, phones, email, addresses, notes and tags. if the street column is empty, no property gets created. only text client custom fields import, not dropdowns, and only jobber's built in lead sources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;jobs&lt;/strong&gt; (&lt;a href="https://help.getjobber.com/en/articles/import-jobs/" rel="noopener noreferrer"&gt;import jobs&lt;/a&gt;, updated 10 sep 2026): this is a &lt;strong&gt;beta&lt;/strong&gt; feature, admin only. up to 1000 jobs per import. past jobs come in as complete and archived, but only if the start date is within ten years. columns include job title, instructions, client name, notes, property address, start date and times, assigned to and total price. client name and the full property address, &lt;strong&gt;including country&lt;/strong&gt;, are mandatory, and jobber warns not every export includes one. no job level custom fields. jobs attach to existing clients only on an exact name or address match, otherwise you get a new client, so import clients first and keep names identical. you get 10 minutes to revert an import.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;attachments&lt;/strong&gt;: neither import doc mentions photos or files. jobber's &lt;a href="https://help.getjobber.com/en/articles/notes-and-attachments/" rel="noopener noreferrer"&gt;notes and attachments&lt;/a&gt; page allows 500 mb per file on notes, but that's manual upload, not an import.&lt;/p&gt;

&lt;h2&gt;
  
  
  what housecall pro can import
&lt;/h2&gt;

&lt;p&gt;from &lt;a href="https://help.housecallpro.com/en/articles/6584603-prepare-to-import-your-data" rel="noopener noreferrer"&gt;prepare to import your data&lt;/a&gt; (updated 23 jan 2026):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;customers&lt;/strong&gt;: customer id, names, company, addresses, phones, emails, notes, tags and lead source. zip codes "must be a real usa zip code", phone numbers must be 10 or 11 digits, and "custom fields cannot be created". anything without a matching field goes into notes or tags first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;jobs&lt;/strong&gt;: the jobs import takes the customer fields plus job id, invoice number, notes, subtotal, tax, payment details, scheduled, start and completion dates and job type. each row has to be &lt;strong&gt;one job&lt;/strong&gt;. if your export spreads a job over several rows (say, one per line item), you have to merge them or only the first row goes in.&lt;/p&gt;

&lt;p&gt;on the max plan, housecall pro's data import team can bring over customers, job history, equipment and price book for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;attachments&lt;/strong&gt;: again, no photo or file field in the import. their &lt;a href="https://help.housecallpro.com/en/articles/1079307-attachments-send-customers-photos-documents" rel="noopener noreferrer"&gt;attachments&lt;/a&gt; page covers uploading files to jobs by hand, and the mobile photo page caps pdfs uploaded from a phone at 5mb. i couldn't find a documented size limit for web uploads or for the import file itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  so what actually moves
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;clients: yes, both, after some column cleanup.&lt;/li&gt;
&lt;li&gt;job records: yes, both, as csv. jobber's is beta and capped at 1000 per file.&lt;/li&gt;
&lt;li&gt;photos, signatures, pdfs: no bulk import in either tool's docs. keep them in your archive, or re upload the few you need onto the right jobs by hand.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;import clients, then a small test batch of jobs, check them, then do the rest. and keep the work zips forever.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;written with ai assistance and checked against verizon's, jobber's and housecall pro's published help docs on 23 oct 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>smallbusiness</category>
      <category>csv</category>
      <category>migration</category>
      <category>backup</category>
    </item>
    <item>
      <title>the PocketOS incident: 4 guards between a coding agent and your prod database</title>
      <dc:creator>labyrinthlab</dc:creator>
      <pubDate>Tue, 27 Oct 2026 13:47:12 +0000</pubDate>
      <link>https://dev.to/labyrinthlab/the-pocketos-incident-4-guards-between-a-coding-agent-and-your-prod-database-3ih8</link>
      <guid>https://dev.to/labyrinthlab/the-pocketos-incident-4-guards-between-a-coding-agent-and-your-prod-database-3ih8</guid>
      <description>&lt;p&gt;in late april 2026, PocketOS founder Jeremy Crane posted that a Cursor agent running Claude Opus 4.6 deleted their production database on Railway. the &lt;a href="https://news.ycombinator.com/item?id=47911524" rel="noopener noreferrer"&gt;Hacker News thread&lt;/a&gt; pointed at his write-up. Railway then published their own account.&lt;/p&gt;

&lt;p&gt;this post is not a blame piece. it asks a practical question: what concrete guards stop a coding agent from wiping a prod database?&lt;/p&gt;

&lt;h2&gt;
  
  
  what the sources say happened
&lt;/h2&gt;

&lt;p&gt;Crane wrote that the agent hit a credential mismatch while working in staging, then called Railway's GraphQL API and deleted a production volume. he said the call took nine seconds and took volume-level backups with it. Decrypt quotes him saying Cursor running Claude Opus 4.6 "deleted our production database and all volume-level backups in a single API call to Railway," and that "it took 9 seconds."&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://decrypt.co/365897/ai-agent-deletes-startup-database-9-seconds-founder-says" rel="noopener noreferrer"&gt;Decrypt&lt;/a&gt;, quoting Crane's X post)&lt;/p&gt;

&lt;p&gt;Railway's engineering post confirms the shape of the call. the agent found a Railway API token on disk and posted a &lt;code&gt;volumeDelete&lt;/code&gt; mutation to &lt;code&gt;backboard.railway.app/graphql/v2&lt;/code&gt;. Railway says the request was authenticated, so the API honored it the same way it would for a CLI or CI job. they also say the token was account-scoped, the broadest option they offer.&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://blog.railway.com/p/your-ai-wants-to-nuke-your-database" rel="noopener noreferrer"&gt;Railway blog&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;on recovery: Railway says they restored the database and the customer came back up with all their data. Crane, quoted by Decrypt, said the most recent recoverable backup he could find at the time was three months old, and that significant data gaps remained. Jake Cooper of Railway told Decrypt they recovered the data about thirty minutes after he connected with Crane, and that a legacy API path had lacked the delayed-delete logic the dashboard already used. Railway later said they patched that endpoint so volume deletes soft-delete for 48 hours, matching the dashboard.&lt;/p&gt;

&lt;p&gt;the wipe path in the sources is a Railway GraphQL &lt;code&gt;volumeDelete&lt;/code&gt; via curl. it is not a Prisma or Drizzle migration command. keep that in mind when you read guard 4.&lt;/p&gt;

&lt;h2&gt;
  
  
  guard 1: the agent never holds prod credentials
&lt;/h2&gt;

&lt;p&gt;the cheapest control is absence. if the agent cannot see a production token or a production &lt;code&gt;DATABASE_URL&lt;/code&gt;, it cannot use them.&lt;/p&gt;

&lt;p&gt;Railway's own post walks through token scopes: account, workspace, project (optionally down to an environment), and OAuth with explicit grants. the token in this incident was account-scoped. narrower tokens already existed; the path of least resistance produced a wider one than the work needed.&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://blog.railway.com/p/your-ai-wants-to-nuke-your-database" rel="noopener noreferrer"&gt;Railway blog&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;practical version for day to day work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep prod URLs and cloud tokens out of the &lt;code&gt;.env&lt;/code&gt; your editor loads&lt;/li&gt;
&lt;li&gt;give the agent a project or environment scoped token that cannot delete prod volumes&lt;/li&gt;
&lt;li&gt;give your local DB role no &lt;code&gt;DROP&lt;/code&gt; rights on shared databases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;rules steer an agent. missing credentials cannot be talked around.&lt;/p&gt;

&lt;h2&gt;
  
  
  guard 2: backups you have restored, including offsite
&lt;/h2&gt;

&lt;p&gt;Railway's volume backup docs are blunt: "Wiping a volume deletes all backups." volume snapshots help after a bad migration. they do not survive deleting the volume itself.&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://docs.railway.com/volumes/backups" rel="noopener noreferrer"&gt;Railway volume backups&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;their Postgres backup guide recommends three layers for production: volume backups, point-in-time recovery (WAL archiving), and logical dumps with &lt;code&gt;pg_dump&lt;/code&gt; that live somewhere else. the guide's restore drill is the point. a backup you have never restored is unverified. Postgres's own docs describe continuous archiving and point-in-time recovery the same way: base backup plus an unbroken WAL archive, then a restore to a chosen time.&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://docs.railway.com/guides/postgres-backups-restores" rel="noopener noreferrer"&gt;Railway Postgres backups&lt;/a&gt;, &lt;a href="https://www.postgresql.org/docs/current/continuous-archiving.html" rel="noopener noreferrer"&gt;Postgres PITR&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;if your only copy sits next to the data, a volume delete is still a bad day. schedule an offsite dump, restore it into a scratch database, and write down how long it took.&lt;/p&gt;

&lt;h2&gt;
  
  
  guard 3: human approval for destructive commands
&lt;/h2&gt;

&lt;p&gt;Cursor hooks can gate shell commands before they run. a &lt;code&gt;beforeShellExecution&lt;/code&gt; hook receives the command string and can return &lt;code&gt;allow&lt;/code&gt;, &lt;code&gt;deny&lt;/code&gt;, or &lt;code&gt;ask&lt;/code&gt;. exit code 2 is also deny. for security-critical hooks, set &lt;code&gt;failClosed: true&lt;/code&gt; so a crash or timeout blocks instead of failing open.&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://cursor.com/docs/agent/hooks" rel="noopener noreferrer"&gt;Cursor hooks&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;use that for the verbs you care about: volume deletes, &lt;code&gt;rm -rf&lt;/code&gt; against known data dirs, forced schema resets, cloud CLIs that destroy databases. &lt;code&gt;ask&lt;/code&gt; puts a person in the loop. &lt;code&gt;deny&lt;/code&gt; forces the agent to stop and explain. one catch from our own testing: with auto-review on, an &lt;code&gt;ask&lt;/code&gt; result could just run the command, so check that in your setup or use &lt;code&gt;deny&lt;/code&gt; for the worst verbs. do not rely on a prompt rule alone. prompts are suggestions. hooks are process checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  guard 4: a hook that steers dangerous migration commands
&lt;/h2&gt;

&lt;p&gt;a different failure mode shows up in everyday agent coding: &lt;code&gt;prisma migrate reset&lt;/code&gt; or &lt;code&gt;drizzle-kit push --force&lt;/code&gt; against a remote &lt;code&gt;DATABASE_URL&lt;/code&gt; that still sits in &lt;code&gt;.env&lt;/code&gt; from last week's debug session. Prisma's own agent consent checks help for some of those commands. they do not cover every tool, and they still depend on you reading which database the URL points at.&lt;/p&gt;

&lt;p&gt;Cursor's &lt;code&gt;beforeShellExecution&lt;/code&gt; hook is the place to steer those commands. one free option is the &lt;a href="https://site-export-rescue.pages.dev/cursor-prisma-drizzle-guard/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=pocketos-guards" rel="noopener noreferrer"&gt;Cursor Prisma / Drizzle Guard page&lt;/a&gt;: a small project hook that denies &lt;code&gt;prisma migrate reset&lt;/code&gt; and &lt;code&gt;drizzle-kit push --force&lt;/code&gt; when the target URL is not local (localhost, 127.0.0.1, file, or sqlite). it matches the agent shell path. it does not block commands you run yourself in your own terminal.&lt;/p&gt;

&lt;p&gt;that hook would not have matched the PocketOS call path in the sources above. the call was Railway GraphQL, not Prisma or Drizzle. put it in place for the wipe modes agents hit during schema work, and pair it with guards 1 through 3 for cloud tokens and recovery.&lt;/p&gt;

&lt;h2&gt;
  
  
  put them together
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;no prod credentials in the agent environment&lt;/li&gt;
&lt;li&gt;backups and PITR you have actually restored, with an offsite dump&lt;/li&gt;
&lt;li&gt;approval (or deny) hooks on destructive shell commands&lt;/li&gt;
&lt;li&gt;a migration hook that steers remote resets and forced pushes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;none of these need a new platform feature. they are ordinary engineering controls that still apply when the operator is a coding agent.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance from the sources linked above, and checked against them.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>postgres</category>
      <category>devops</category>
    </item>
    <item>
      <title>tu sitio de weebly quedó fuera de línea: cómo convertir el zip del archivo en un sitio estático gratis</title>
      <dc:creator>labyrinthlab</dc:creator>
      <pubDate>Mon, 26 Oct 2026 13:46:17 +0000</pubDate>
      <link>https://dev.to/labyrinthlab/tu-sitio-de-weebly-quedo-fuera-de-linea-como-convertir-el-zip-del-archivo-en-un-sitio-estatico-565h</link>
      <guid>https://dev.to/labyrinthlab/tu-sitio-de-weebly-quedo-fuera-de-linea-como-convertir-el-zip-del-archivo-en-un-sitio-estatico-565h</guid>
      <description>&lt;p&gt;la respuesta corta: sí. si tenías un sitio de weebly en uno de los países afectados y ya no aparece en línea, todavía puedes descargar el archivo zip del sitio desde el editor y convertirlo en un sitio estático que funciona en un hosting gratuito. no necesitas que el sitio siga publicado, porque todo sale de los archivos del zip. lo que sí necesitas es entrar a tu cuenta antes del 26 de diciembre de 2026.&lt;/p&gt;

&lt;p&gt;un poco de contexto, porque se está repitiendo mucho algo que no es cierto. el cambio afecta a weebly solo en 67 países, no en todo el mundo. en esos países los sitios fueron despublicados el 27 de septiembre de 2026, y el acceso a la cuenta (y con él la exportación) termina el 26 de diciembre de 2026. en la lista hay varios países de habla hispana: colombia, chile, costa rica, ecuador, perú, uruguay y paraguay, entre otros. la lista completa está en el &lt;a href="https://www.weebly.com/app/help/us/en/topics/changes-to-weebly-global-operations" rel="noopener noreferrer"&gt;aviso de weebly&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  cómo descargar el archivo
&lt;/h2&gt;

&lt;p&gt;en el editor del sitio entra a &lt;strong&gt;settings &amp;gt; general&lt;/strong&gt;, baja hasta &lt;strong&gt;archive&lt;/strong&gt;, escribe tu correo y haz clic en &lt;strong&gt;email archive&lt;/strong&gt;. te llega un correo con el enlace de descarga. weebly lo explica en su página &lt;a href="https://www.weebly.com/app/help/us/en/topics/back-up-your-site" rel="noopener noreferrer"&gt;back up your site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;no lo confundas con &lt;strong&gt;download my data&lt;/strong&gt;, en la configuración de la cuenta. es otro archivo con otro formato. baja los dos, pero el que se convierte en sitio web es el zip del archivo. ábrelo apenas llegue para confirmar que no está vacío.&lt;/p&gt;

&lt;h2&gt;
  
  
  qué trae el zip y qué no
&lt;/h2&gt;

&lt;p&gt;abrí un zip real de un sitio de prueba de cuatro páginas, exportado el 8 de octubre de 2026. adentro hay una sola carpeta con un nombre largo (&lt;code&gt;17027946326ac7e6d662b26/&lt;/code&gt;), y esa es la raíz del sitio. ahí están &lt;code&gt;index.html&lt;/code&gt;, &lt;code&gt;about.html&lt;/code&gt;, &lt;code&gt;gallery.html&lt;/code&gt; y &lt;code&gt;contact.html&lt;/code&gt;, la carpeta &lt;code&gt;uploads/&lt;/code&gt; con las imágenes, &lt;code&gt;files/main_style.css&lt;/code&gt;, los scripts del tema en &lt;code&gt;files/theme/files/&lt;/code&gt; y un reproductor de audio flash en &lt;code&gt;apps/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;un detalle: en &lt;code&gt;uploads/&lt;/code&gt; varias imágenes aparecen dos veces, por ejemplo &lt;code&gt;berry-tart.jpg&lt;/code&gt; y &lt;code&gt;berry-tart_orig.jpg&lt;/code&gt;. son dos tamaños de la misma foto, así que no borres ninguna.&lt;/p&gt;

&lt;p&gt;lo que no viene es el blog ni la tienda. según la página de respaldo de weebly, las páginas de blog y de tienda no se incluyen en la exportación porque dependen directamente de su base de datos. la misma página avisa que el formulario de contacto y los slideshows pueden no funcionar fuera del editor, y que el zip no se puede volver a importar en weebly. si tenías blog, copia las entradas desde el editor mientras puedas entrar.&lt;/p&gt;

&lt;h2&gt;
  
  
  qué falla si lo subes tal cual
&lt;/h2&gt;

&lt;p&gt;en el html del zip que revisé, cada página carga los estilos, las fuentes y los scripts compartidos de weebly desde &lt;code&gt;cdn2.editmysite.com&lt;/code&gt; y &lt;code&gt;cdn11.editmysite.com&lt;/code&gt; con &lt;code&gt;http://&lt;/code&gt;, once veces por página. cualquier hosting estático gratuito sirve tu sitio con https, y los navegadores bloquean esos archivos por contenido mixto. el resultado es una página sin estilos.&lt;/p&gt;

&lt;p&gt;además, las páginas piden &lt;code&gt;files/theme/plugins.js&lt;/code&gt; y &lt;code&gt;files/theme/custom.js&lt;/code&gt;, pero en el zip esos archivos están en &lt;code&gt;files/theme/files/&lt;/code&gt;. el menú móvil depende de esos scripts, y en mi prueba solo abrió en el teléfono después de corregir el https y las rutas. y el formulario de contacto envía a &lt;code&gt;///weebly/apps/formSubmit.php&lt;/code&gt;, que solo funcionaba dentro de weebly.&lt;/p&gt;

&lt;h2&gt;
  
  
  dos maneras de convertirlo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;a mano.&lt;/strong&gt; trabaja sobre una copia de la carpeta. el arreglo de https es un reemplazo en todos los html:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# dentro de la carpeta del sitio. en macos usa: sed -i '' ...&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s#http://cdn#https://cdn#g'&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;el de los scripts del tema también lo haces tú: mueves los dos archivos una carpeta arriba o corriges las rutas en cada html. el formulario lo reemplazas por un enlace &lt;code&gt;mailto:&lt;/code&gt; o por un servicio de formularios, después de exportar las entradas viejas desde el panel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;con una herramienta gratuita.&lt;/strong&gt; &lt;a href="https://archive-to-static.pages.dev/es/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=tu-sitio-de-weebly-quedo-fuera-de-linea-como-convertir-el-zip-del-archivo-en-un-sitio-estatico-565h" rel="noopener noreferrer"&gt;archive to static&lt;/a&gt; es una herramienta de navegador que lee el zip del archivo del sitio. tu exportación se queda en tu navegador. convierte los enlaces &lt;code&gt;http://&lt;/code&gt; a https, quita los scripts del editor y la insignia de weebly, y cambia los formularios que ya no funcionan por un aviso con un enlace de correo si le das una dirección. con el tema hace algo distinto a lo que harías a mano: no corrige las rutas de esos scripts, sino que quita los scripts del tema (&lt;code&gt;plugins.js&lt;/code&gt; y &lt;code&gt;custom.js&lt;/code&gt;) y agrega su propio código para el menú móvil. o sea, el menú abre, pero los scripts originales del tema ya no están. el blog y la tienda tampoco aparecen con la herramienta, porque no están en el zip; los lista en el informe como páginas que faltan.&lt;/p&gt;

&lt;h2&gt;
  
  
  dónde alojarlo gratis
&lt;/h2&gt;

&lt;p&gt;en &lt;a href="https://developers.cloudflare.com/pages/get-started/direct-upload/" rel="noopener noreferrer"&gt;cloudflare pages&lt;/a&gt; puedes arrastrar la carpeta o el zip. el límite es de 1.000 archivos y 25 MiB por archivo. en &lt;a href="https://docs.netlify.com/start/quickstarts/netlify-drop-quickstart/" rel="noopener noreferrer"&gt;netlify drop&lt;/a&gt; arrastras la carpeta, pero inicia sesión antes: si lo subes sin cuenta, el sitio queda protegido con una contraseña temporal hasta que lo reclames. sube el contenido de la carpeta de nombre largo, para que &lt;code&gt;index.html&lt;/code&gt; quede en la raíz.&lt;/p&gt;

&lt;p&gt;si compraste el dominio con weebly, empieza la transferencia antes del 26 de diciembre desde el panel de dominios. no cambies los datos de contacto del dominio antes de transferirlo, porque eso activa un bloqueo de 60 días.&lt;/p&gt;

&lt;p&gt;lo honesto: no recuperas el blog, ni la tienda, ni el reproductor flash, y vas a tener un sitio sin editor, así que cambiar un teléfono significa editar html. para un sitio de presentación que casi no tocabas, igual es la forma más rápida de tenerlo de vuelta en línea, gratis y con el mismo aspecto. lo urgente es descargar el zip ahora, aunque lo conviertas el mes que viene.&lt;/p&gt;




&lt;p&gt;Escrito con ayuda de IA y verificado contra un archivo real de un sitio de Weebly y la documentación de Weebly, Cloudflare y Netlify.&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>webdev</category>
      <category>migration</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>leaving solo (soloist.ai): wordpress, carrd or a static host? how to pick before 30 nov</title>
      <dc:creator>labyrinthlab</dc:creator>
      <pubDate>Sat, 24 Oct 2026 13:31:15 +0000</pubDate>
      <link>https://dev.to/labyrinthlab/leaving-solo-soloistai-wordpress-carrd-or-a-static-host-how-to-pick-before-30-nov-4ii8</link>
      <guid>https://dev.to/labyrinthlab/leaving-solo-soloistai-wordpress-carrd-or-a-static-host-how-to-pick-before-30-nov-4ii8</guid>
      <description>&lt;p&gt;solo, mozilla's ai site builder, deletes everything on 30 nov 2026. its &lt;a href="https://support.soloist.ai/doc/solo-shutdown-faq" rel="noopener noreferrer"&gt;shutdown faq&lt;/a&gt; is blunt about it: "There will be no way to recover your site, account, or data after that date."&lt;/p&gt;

&lt;p&gt;the export steps are in &lt;a href="https://dev.to/labyrinthlab/solo-soloistai-is-shutting-down-on-30-nov-how-to-get-your-site-out-images-included-40gp"&gt;my earlier post&lt;/a&gt;, so i won't repeat them. this one is the question people ask after they have the zip: ok, where does it go?&lt;/p&gt;

&lt;p&gt;solo's faq suggests wix, squarespace, wordpress or ai builders. i'll compare three that fit most small solo sites: wordpress.com, carrd, and a free static host.&lt;/p&gt;

&lt;h2&gt;
  
  
  first, what you're actually carrying
&lt;/h2&gt;

&lt;p&gt;per solo, the zip has "an HTML export of your website and a CSV file with links to your image assets," plus form and newsletter submissions. the photos aren't in it. they come from the csv links, which die with solo on 30 nov, whichever destination you pick. so grab the images first, decide later. and the submissions are customer emails, they stay on your own machine.&lt;/p&gt;

&lt;p&gt;the real difference between the three: does it take your html as is, or do you rebuild?&lt;/p&gt;

&lt;h2&gt;
  
  
  wordpress.com: rebuild, but you get an editor
&lt;/h2&gt;

&lt;p&gt;there's no solo importer. wordpress.com's &lt;a href="https://wordpress.com/support/import/converting-an-html-site-to-wordpress/" rel="noopener noreferrer"&gt;import from other platforms&lt;/a&gt; guide is a copy-paste job: add your images to the media library, then copy "the main text of a single page" into a new page, since copying the whole page "may make some unwanted HTML." menus get rebuilt separately.&lt;/p&gt;

&lt;p&gt;so your text and image files carry over. the solo look doesn't, you pick a theme. forms don't either.&lt;/p&gt;

&lt;p&gt;cost, from the &lt;a href="https://wordpress.com/pricing/" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; today: the free plan is a yoursite.wordpress.com subdomain with 1 gb of storage, and visitors see ads. connecting a domain you already own is a paid feature, Personal and up (&lt;a href="https://wordpress.com/support/domains/connect-existing-domain/" rel="noopener noreferrer"&gt;connect a domain&lt;/a&gt;). Personal is us$4/month billed yearly, or us$9 month to month. annual plans include a free domain for the first year, monthly ones don't.&lt;/p&gt;

&lt;p&gt;fits you if: you'll keep editing, want a blog, or someone non-technical updates it. effort: an afternoon for a few pages, more if you're picky about design.&lt;/p&gt;

&lt;h2&gt;
  
  
  carrd: rebuild, small and cheap
&lt;/h2&gt;

&lt;p&gt;carrd builds "one-page sites," so it suits solo sites that were really one long page: a landing page, a portfolio, a link-in-bio thing. no html import here either, you rebuild with text elements and upload your images. even carrd's own &lt;a href="https://carrd.com/docs/sites/downloading" rel="noopener noreferrer"&gt;downloads&lt;/a&gt; "cannot be uploaded back to the Carrd platform."&lt;/p&gt;

&lt;p&gt;cost is where carrd is nice. the free plan lets you "build up to three sites per account" on a .carrd.co address (&lt;a href="https://carrd.co" rel="noopener noreferrer"&gt;carrd.co&lt;/a&gt;). your own domain needs Pro Standard, $19/year (&lt;a href="https://carrd.com/docs/pro/plans" rel="noopener noreferrer"&gt;plans&lt;/a&gt;, &lt;a href="https://carrd.com/docs/sites/using-a-custom-domain" rel="noopener noreferrer"&gt;custom domain&lt;/a&gt;). Pro Lite at $9/year has no custom domain. a contact form also needs Pro Standard (&lt;a href="https://carrd.com/docs/forms/setting-up-a-contact-form" rel="noopener noreferrer"&gt;contact form&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;fits you if: it's one page and you want an editor without all of wordpress. effort: the quickest rebuild, if it really is one page. five separate pages gets clunky.&lt;/p&gt;

&lt;h2&gt;
  
  
  a static host: keep the html, lose the editor
&lt;/h2&gt;

&lt;p&gt;the only option that uses your export as is. once the html points at your local images instead of solo's links, you upload the folder and it looks basically the same.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.cloudflare.com/pages/get-started/direct-upload/" rel="noopener noreferrer"&gt;cloudflare pages direct upload&lt;/a&gt; takes a folder or a zip by drag and drop, up to 1,000 files and 25 MiB per file. cloudflare says all plans come with "unlimited sites, seats, requests, and bandwidth" (&lt;a href="https://pages.cloudflare.com/" rel="noopener noreferrer"&gt;pages&lt;/a&gt;). one gotcha: a direct upload project can't be switched to git later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.netlify.com/start/quickstarts/netlify-drop-quickstart/" rel="noopener noreferrer"&gt;netlify drop&lt;/a&gt; is the same idea. log in first, or the site sits behind a temporary password until you claim it. the free plan is $0 with a 300 credit limit per month, and custom domains are included (&lt;a href="https://www.netlify.com/pricing/" rel="noopener noreferrer"&gt;pricing&lt;/a&gt;). a production deploy costs 15 credits and bandwidth is 20 credits per gb, and if you hit the limit your projects pause until the next cycle. plenty for a small brochure site, but cloudflare is safer if you're worried about a traffic spike.&lt;/p&gt;

&lt;p&gt;what doesn't carry: anything that needed solo running, like the contact form (use an email link or a form service). and no editor, so new opening hours means editing html.&lt;/p&gt;

&lt;p&gt;fits you if: the site is done and you just want it up, free, looking the same. effort: under an hour once the images are sorted.&lt;/p&gt;

&lt;p&gt;i wrote a longer walkthrough with the netlify and cloudflare steps and a what-carries-over table &lt;a href="https://site-export-rescue.pages.dev/move-solo-to-static-host/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=leaving-solo-soloistai-wordpress-carrd-or-a-static-host-how-to-pick-before-30-nov-4ii8" rel="noopener noreferrer"&gt;here&lt;/a&gt;, and it links the wordpress and carrd versions too. it's built around the zip from our free tool, so heads up, that tool fetches your images through our server.&lt;/p&gt;

&lt;h2&gt;
  
  
  the domain move
&lt;/h2&gt;

&lt;p&gt;if you bought the domain through solo domains (not ionos), it moves to name.com first: fill in your name.com contacts, then email solo your account code (steps in the faq linked up top). ionos or anywhere else, just log in there.&lt;/p&gt;

&lt;p&gt;then each option wants something different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wordpress.com: a paid plan, then their connect-a-domain flow.&lt;/li&gt;
&lt;li&gt;carrd: Pro Standard, then the A record for @ and CNAME for www it shows you. can take up to an hour.&lt;/li&gt;
&lt;li&gt;cloudflare pages: a bare domain like example.com must be a zone in your cloudflare account (nameservers pointed at cloudflare). a subdomain just needs a CNAME, but add it in pages first or you get a 522 (&lt;a href="https://developers.cloudflare.com/pages/configuration/custom-domains/" rel="noopener noreferrer"&gt;custom domains&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;netlify: add it in netlify first, then a CNAME for subdomains and an ALIAS, ANAME or A record for the bare domain (&lt;a href="https://docs.netlify.com/manage/domains/configure-domains/configure-external-dns" rel="noopener noreferrer"&gt;external dns&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and leave the mx records alone if you get email on that domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  so which
&lt;/h2&gt;

&lt;p&gt;one page, want an editor: carrd. many pages, want to keep editing or blogging: wordpress, and treat it as a rebuild. it's finished and you just want it alive: static host, free.&lt;/p&gt;

&lt;p&gt;none of it matters if the images are gone though. get them out of the csv links this week. the destination can wait, the export can't.&lt;/p&gt;




&lt;p&gt;Written with AI assistance and checked against Solo's shutdown FAQ and the WordPress.com, Carrd, Cloudflare Pages and Netlify pricing and docs, all read on 23 Oct 2026.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>wordpress</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>moving off weebly: wordpress or a free static host? what the archive zip actually allows</title>
      <dc:creator>labyrinthlab</dc:creator>
      <pubDate>Fri, 23 Oct 2026 13:34:53 +0000</pubDate>
      <link>https://dev.to/labyrinthlab/moving-off-weebly-wordpress-or-a-free-static-host-what-the-archive-zip-actually-allows-h6f</link>
      <guid>https://dev.to/labyrinthlab/moving-off-weebly-wordpress-or-a-free-static-host-what-the-archive-zip-actually-allows-h6f</guid>
      <description>&lt;p&gt;quick context. weebly is winding down in 67 countries, not everywhere. sites there were unpublished on 27 sep 2026, and login (and with it the export) ends on 26 dec 2026. the us, uk and eu aren't on the list. details are on &lt;a href="https://www.weebly.com/app/help/us/en/topics/changes-to-weebly-global-operations" rel="noopener noreferrer"&gt;weebly's notice&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;i already wrote up &lt;a href="https://dev.to/labyrinthlab/how-to-move-a-weebly-site-to-a-free-static-host-before-the-export-window-closes-3j2d"&gt;the hand-fix route for a static host&lt;/a&gt;. the question i keep getting after that one: should the archive go into wordpress instead? so i opened a real archive and checked what each route can do with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  what's actually in the zip
&lt;/h2&gt;

&lt;p&gt;this is a real site archive from a small 4-page test site, exported on 8 oct 2026. one top-level folder with a long id for a name (&lt;code&gt;17027946326ac7e6d662b26/&lt;/code&gt;), and inside it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;index.html&lt;/code&gt;, &lt;code&gt;about.html&lt;/code&gt;, &lt;code&gt;gallery.html&lt;/code&gt;, &lt;code&gt;contact.html&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uploads/1/5/6/4/156416411/&lt;/code&gt; with about 20 images, most in two sizes (&lt;code&gt;berry-tart.jpg&lt;/code&gt; and &lt;code&gt;berry-tart_orig.jpg&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;files/main_style.css&lt;/code&gt;, plus &lt;code&gt;files/theme/files/&lt;/code&gt; with &lt;code&gt;custom.js&lt;/code&gt;, &lt;code&gt;plugins.js&lt;/code&gt;, &lt;code&gt;manifest.json&lt;/code&gt; and a few theme images&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apps/audioPlayer2.swf&lt;/code&gt;, a flash audio player&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;that's it. no favicon, no xml, no csv, no database dump. no blog or store either, and that's by design: weebly's &lt;a href="https://www.weebly.com/app/help/us/en/topics/back-up-your-site" rel="noopener noreferrer"&gt;back up your site&lt;/a&gt; page says blog and store pages aren't included because they rely on its database, that the contact form and slideshow may not work outside the editor, and that the zip can't be imported back into weebly.&lt;/p&gt;

&lt;p&gt;two more things from the html itself. each page loads weebly's shared css, fonts and scripts from &lt;code&gt;cdn2.editmysite.com&lt;/code&gt; and &lt;code&gt;cdn11.editmysite.com&lt;/code&gt;, with 11 &lt;code&gt;http://cdn&lt;/code&gt; references per page. and the contact form posts to &lt;code&gt;///weebly/apps/formSubmit.php&lt;/code&gt;, which only ever worked on weebly.&lt;/p&gt;

&lt;p&gt;that list decides most of what follows.&lt;/p&gt;

&lt;h2&gt;
  
  
  the wordpress route
&lt;/h2&gt;

&lt;p&gt;there's no weebly importer. the wordpress.org &lt;a href="https://developer.wordpress.org/advanced-administration/wordpress/import/" rel="noopener noreferrer"&gt;importing content&lt;/a&gt; page covers blogger, tumblr, movable type, rss, another wordpress site (via a WXR file) and plugins for a bunch of other systems. weebly isn't on it, and the archive has nothing those importers read. it's plain html files.&lt;/p&gt;

&lt;p&gt;so moving this zip to wordpress means rebuilding. wordpress.com's own &lt;a href="https://wordpress.com/support/import/converting-an-html-site-to-wordpress/" rel="noopener noreferrer"&gt;html site guide&lt;/a&gt; says to copy only the main text of each page, since pasting the whole page brings unwanted html along, and to add your images separately.&lt;/p&gt;

&lt;p&gt;what comes over: the text (by copy-paste), the images (upload them from &lt;code&gt;uploads/&lt;/code&gt; to the media library) and your page structure. what doesn't: the look, since &lt;code&gt;main_style.css&lt;/code&gt; is a weebly theme and not a wordpress one, the form, the flash player, and of course the blog and store that were never in the zip.&lt;/p&gt;

&lt;p&gt;what you get back is an editor, a real blog going forward, form plugins, and woocommerce if you want to rebuild the store. if you post or edit often, that matters more than keeping the old design.&lt;/p&gt;

&lt;p&gt;cost: the wordpress.com free plan is a wordpress.com subdomain with ads and 1 gb of storage, and connecting a domain you already own needs a paid plan (&lt;a href="https://wordpress.com/pricing/" rel="noopener noreferrer"&gt;pricing&lt;/a&gt;, &lt;a href="https://wordpress.com/support/domains/connect-existing-domain/" rel="noopener noreferrer"&gt;connect a domain&lt;/a&gt;). self-hosted wordpress is free software, but you pay for hosting and you keep it updated.&lt;/p&gt;

&lt;h2&gt;
  
  
  the static host route
&lt;/h2&gt;

&lt;p&gt;here you upload the html you already have, so the site keeps its look. on &lt;a href="https://developers.cloudflare.com/pages/get-started/direct-upload/" rel="noopener noreferrer"&gt;cloudflare pages direct upload&lt;/a&gt; you drag in a folder or a zip. drag and drop allows 1,000 files and 25 MiB per file, and a direct upload project can't be switched to git later. on &lt;a href="https://docs.netlify.com/start/quickstarts/netlify-drop-quickstart/" rel="noopener noreferrer"&gt;netlify drop&lt;/a&gt; you drag in the folder, but log in first: a drop without signing in sits behind a temporary password until you claim it. both have free plans, and my test archive (46 entries, about 4 mb) is nowhere near the limits.&lt;/p&gt;

&lt;p&gt;"as-is" isn't quite as-is, though. on https those &lt;code&gt;http://cdn&lt;/code&gt; links get blocked as mixed content. and the pages ask for &lt;code&gt;files/theme/plugins.js&lt;/code&gt; and &lt;code&gt;files/theme/custom.js&lt;/code&gt; while the files sit one folder deeper, in &lt;code&gt;files/theme/files/&lt;/code&gt;. so on a phone the menu won't open until you deal with it. the earlier post has the find-and-replace for both.&lt;/p&gt;

&lt;p&gt;i also ran this archive through the free browser tool i made for this. on this zip it upgraded the links to https, saved the editmysite css and fonts into &lt;code&gt;assets/vendor/&lt;/code&gt;, stripped the theme's scripts and added its own small mobile menu (&lt;code&gt;assets/ats/nav.js&lt;/code&gt;), left out the &lt;code&gt;.swf&lt;/code&gt;, and swapped the contact form for a note saying it can't send. it does not fix the theme script paths. it removes those scripts instead, so if your theme leaned on them for more than the menu, check that by hand. i wrote up what to do with that output for each route, including the wordpress copy-paste steps, &lt;a href="https://archive-to-static.pages.dev/move-weebly-to-wordpress/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=moving-off-weebly-wordpress-or-a-free-static-host-what-the-archive-zip-actually-allows-h6f" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;the catch with static: there's no editor. changing a phone number means editing html, and there's no blog going forward unless you add a static site generator.&lt;/p&gt;

&lt;h2&gt;
  
  
  gotchas for both
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;blog and store.&lt;/strong&gt; neither route recovers them, because they aren't in the zip. copy posts out of the editor while you can still log in, and export the store separately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;forms.&lt;/strong&gt; dead either way. on wordpress add a form plugin, on a static host use a form service or a &lt;code&gt;mailto:&lt;/code&gt; link. export your old form entries first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;domains.&lt;/strong&gt; if you bought it through weebly, start the transfer from the weebly dashboard before 26 dec. the notice also says a domain can't be transferred within 60 days of registration. if it lives at another registrar, add it in the new host first, then change dns (&lt;a href="https://developers.cloudflare.com/pages/configuration/custom-domains/" rel="noopener noreferrer"&gt;cloudflare pages custom domains&lt;/a&gt;, &lt;a href="https://docs.netlify.com/manage/domains/configure-domains/configure-external-dns" rel="noopener noreferrer"&gt;netlify external dns&lt;/a&gt;). on wordpress.com that's the paid-plan step above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;flash.&lt;/strong&gt; &lt;code&gt;audioPlayer2.swf&lt;/code&gt; won't play on either. modern browsers dropped flash.&lt;/p&gt;

&lt;h2&gt;
  
  
  so which one
&lt;/h2&gt;

&lt;p&gt;if it was a brochure site you rarely touched, a static host gets it back online today, free, looking the same. if you blog, sell, or someone non-technical edits it every week, go wordpress and treat it as a rebuild, with the zip as your source of text and images.&lt;/p&gt;

&lt;p&gt;either way, download the archive before 26 dec. neither route works without it.&lt;/p&gt;




&lt;p&gt;Written with AI assistance and checked against a real Weebly site archive and the Weebly, WordPress, Cloudflare and Netlify docs.&lt;/p&gt;

</description>
      <category>weebly</category>
      <category>wordpress</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>your cursor hook says ask. in auto-review, the command may just run</title>
      <dc:creator>labyrinthlab</dc:creator>
      <pubDate>Sat, 17 Oct 2026 16:16:58 +0000</pubDate>
      <link>https://dev.to/labyrinthlab/your-cursor-hook-says-ask-in-auto-review-the-command-may-just-run-36b9</link>
      <guid>https://dev.to/labyrinthlab/your-cursor-hook-says-ask-in-auto-review-the-command-may-just-run-36b9</guid>
      <description>&lt;p&gt;say you wrote a cursor hook to catch the scary database commands, like &lt;code&gt;prisma migrate reset&lt;/code&gt; or &lt;code&gt;drizzle-kit push --force&lt;/code&gt;. you made it answer "ask", because you want a human to look before it runs, not a hard no.&lt;/p&gt;

&lt;p&gt;in two of cursor's run modes, that works the way you'd expect. in auto-review mode, it may not. the command can just run with no prompt.&lt;/p&gt;

&lt;p&gt;this post covers what was reported, what cursor staff said about it, and what i'd return instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  what was reported
&lt;/h2&gt;

&lt;p&gt;the report is &lt;a href="https://forum.cursor.com/t/173694" rel="noopener noreferrer"&gt;cursor forum topic 173694&lt;/a&gt;, "beforeShellExecution hook returning ask is ignored in Auto-review mode (honoured in Allowlist and Run Everything), 3.21.16", posted 4 oct 2026. the reporter was on cursor 3.21.16 on windows, with a project-level hook that always answers ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"permission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"ask"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"user_message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"hook asked"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"agent_message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"hook asked"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then they asked the agent to run &lt;code&gt;echo test &amp;gt; t.txt&lt;/code&gt; in each mode. their summary:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A project-level beforeShellExecution hook that answers {"permission":"ask"} gets the approval prompt in Allowlist mode and in Run Everything mode ("Hook requested approval: …"). In Auto-review mode the command runs without any prompt. deny is enforced in all three modes."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;their table, cleaned up:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;auto-run mode&lt;/th&gt;
&lt;th&gt;hook answers ask&lt;/th&gt;
&lt;th&gt;hook answers deny&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;allowlist&lt;/td&gt;
&lt;td&gt;prompt&lt;/td&gt;
&lt;td&gt;not tested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;auto-review&lt;/td&gt;
&lt;td&gt;no prompt, command ran (two trials)&lt;/td&gt;
&lt;td&gt;blocked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;run everything&lt;/td&gt;
&lt;td&gt;prompt: "Hook requested approval"&lt;/td&gt;
&lt;td&gt;not tested&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;they also noted that the hook payload doesn't say which auto-run mode is active, "so a hook can't detect Auto-review and compensate."&lt;/p&gt;

&lt;h2&gt;
  
  
  what cursor staff said
&lt;/h2&gt;

&lt;p&gt;a cursor community support engineer &lt;a href="https://forum.cursor.com/t/173694" rel="noopener noreferrer"&gt;replied in the same thread&lt;/a&gt; on 5 oct 2026:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"You're right: in Auto-review, when Auto-review itself decides a command is safe to run, the hook's "ask" answer currently isn't applied, so no approval prompt appears. "deny" is still enforced in every mode, as you saw. I've flagged this to the team."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and for the meantime:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"If you need to stay in Auto-review, have the hook return "deny" for the commands that must never run without review. The agent will see the hook's message and can ask you before retrying."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;the other option they gave was to use allowlist or run everything, where an ask always shows the prompt.&lt;/p&gt;

&lt;p&gt;the important bit is the qualifier: ask gets dropped &lt;em&gt;when auto-review decides the command is safe&lt;/em&gt;. so you can't count on ask in that mode. as of 17 oct the thread doesn't say it's been fixed, but i haven't re-tested on a newer build, so check yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  one more thing: which message reaches the agent
&lt;/h2&gt;

&lt;p&gt;a different thread, &lt;a href="https://forum.cursor.com/t/174902" rel="noopener noreferrer"&gt;cursor forum topic 174902&lt;/a&gt; (reporter on cursor 3.24.18), got a staff reply on 16 oct 2026 that matters if you write deny messages:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"when a hook denies a command, only user_message reaches the agent right now (agent_message is dropped)"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Put any instructions meant for the agent in user_message"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;so when you deny, put the "here's what to do instead" text in &lt;code&gt;user_message&lt;/code&gt;. the hook below writes the same text into both fields, so it's fine either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  so: deny, with a useful message
&lt;/h2&gt;

&lt;p&gt;for commands that should never run without a person, return deny and say what the agent should do next. the &lt;a href="https://cursor.com/docs/agent/hooks" rel="noopener noreferrer"&gt;cursor hooks docs&lt;/a&gt; also say exit code 2 counts as deny, and that crashes, timeouts and other non-zero exits fail open unless you set &lt;code&gt;failClosed: true&lt;/code&gt;. so set it.&lt;/p&gt;

&lt;p&gt;here's the hook. it's 27 lines of node with no dependencies, because if you're on prisma or drizzle you already have node.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.cursor/hooks/deny-db-wipes.mjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// beforeShellExecution hook: deny commands that can wipe a database.&lt;/span&gt;
&lt;span class="c1"&gt;// cursor sends JSON on stdin and reads JSON from stdout.&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deny&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;the db-wipe hook could not read the hook payload, so it blocked this command. run it yourself if you meant it.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;prisma&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+migrate&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+reset&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prisma migrate reset drops the database&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;prisma&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+db&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+push&lt;/span&gt;&lt;span class="se"&gt;\b[^&lt;/span&gt;&lt;span class="sr"&gt;;&amp;amp;|&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;--&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;force-reset|accept-data-loss&lt;/span&gt;&lt;span class="se"&gt;)\b&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prisma db push with --force-reset or --accept-data-loss can drop data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;drizzle-kit&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+push&lt;/span&gt;&lt;span class="se"&gt;\b[^&lt;/span&gt;&lt;span class="sr"&gt;;&amp;amp;|&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\s(&lt;/span&gt;&lt;span class="sr"&gt;--force|-f&lt;/span&gt;&lt;span class="se"&gt;)\b&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;drizzle-kit push --force auto-approves data loss statements&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deny&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`blocked by the db-wipe hook: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;. do not retry with a different flag or wrapper. `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s2"&gt;`tell the user the exact command and which database it targets, and let them run it themselves.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;permission&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;agent_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.cursor/hooks.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"beforeShellExecution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node .cursor/hooks/deny-db-wipes.mjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"failClosed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prisma|drizzle-kit"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the matcher means cursor only runs the hook when the command text contains &lt;code&gt;prisma&lt;/code&gt; or &lt;code&gt;drizzle-kit&lt;/code&gt;. per the docs, beforeShellExecution matchers are tested against the full command string. project hooks run from the project root, which is why the path is &lt;code&gt;.cursor/hooks/...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;the drizzle message quotes drizzle-kit's own help text for &lt;code&gt;--force&lt;/code&gt;: "Auto-approve all data loss statements. Note: Data loss statements may truncate your tables and data" (checked in drizzle-kit 0.31.11).&lt;/p&gt;

&lt;h2&gt;
  
  
  test it without cursor
&lt;/h2&gt;

&lt;p&gt;you don't need the agent to check the logic. pipe fake payloads in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;t&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'{"command":%s,"cwd":"","sandbox":false}'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;node &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'JSON.stringify(process.argv[1])'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    | node .cursor/hooks/deny-db-wipes.mjs
&lt;span class="o"&gt;}&lt;/span&gt;
t &lt;span class="s2"&gt;"npx drizzle-kit push --force"&lt;/span&gt;
t &lt;span class="s2"&gt;"npx drizzle-kit push"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here's what i got on node 20 with a batch of commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx prisma migrate reset                              -&amp;gt; deny
npx prisma migrate reset --force                      -&amp;gt; deny
pnpm exec prisma migrate reset -f                     -&amp;gt; deny
npx prisma migrate dev                                -&amp;gt; allow
npx prisma migrate deploy                             -&amp;gt; allow
npx prisma db push                                    -&amp;gt; allow
npx prisma db push --accept-data-loss                 -&amp;gt; deny
bunx prisma db push --force-reset                     -&amp;gt; deny
npx drizzle-kit push                                  -&amp;gt; allow
npx drizzle-kit push --force                          -&amp;gt; deny
npx drizzle-kit push -f                               -&amp;gt; deny
npx drizzle-kit push --strict --force                 -&amp;gt; deny
npx drizzle-kit generate &amp;amp;&amp;amp; npx drizzle-kit migrate   -&amp;gt; allow
cd app &amp;amp;&amp;amp; npx drizzle-kit push --force                -&amp;gt; deny
bash -c 'npx prisma migrate reset --force'            -&amp;gt; deny
npm run db:reset                                      -&amp;gt; allow   (miss)
echo prisma migrate reset                             -&amp;gt; deny    (false positive)
npx drizzle-kit push --forceful                       -&amp;gt; allow
not json                                              -&amp;gt; deny    (fails closed)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that's a test of the script, not of cursor. i ran these payloads through node on a linux box. i didn't run them through a live cursor agent in each mode, so check the end-to-end behavior on your own build.&lt;/p&gt;

&lt;h2&gt;
  
  
  the gaps, honestly
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;wrappers get past it.&lt;/strong&gt; &lt;code&gt;npm run db:reset&lt;/code&gt; doesn't contain &lt;code&gt;prisma&lt;/code&gt; or &lt;code&gt;drizzle-kit&lt;/code&gt;, so the matcher never even calls the hook. the same goes for a makefile target or a shell script. either add your script names to the matcher and the rules, or keep destructive scripts out of &lt;code&gt;package.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;it only sees text.&lt;/strong&gt; the hook doesn't know which database &lt;code&gt;DATABASE_URL&lt;/code&gt; points at. &lt;code&gt;prisma migrate reset&lt;/code&gt; on a throwaway local db gets blocked too, and you run it yourself. that's the trade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;regex isn't a shell parser.&lt;/strong&gt; quoting, variables (&lt;code&gt;$CMD&lt;/code&gt;), &lt;code&gt;eval&lt;/code&gt; and odd spacing can slip through. it also over-blocks things like &lt;code&gt;echo prisma migrate reset&lt;/code&gt;. that's fine for a guard, just know it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;deny is blunter than ask.&lt;/strong&gt; in allowlist and run everything mode, ask would have given you a nice prompt. with deny you copy the command and run it yourself. the reporter in 173694 asked for the auto-run mode to be added to the payload so a hook could deny only in auto-review. until something like that ships, deny is the answer that holds in every mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;the model can still argue with a deny.&lt;/strong&gt; a deny stops that one command. it doesn't stop the agent trying another route, like raw sql through psql. the message tells it not to, but that's an instruction, not enforcement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;behavior can change.&lt;/strong&gt; this was reported on 3.21.16 and acknowledged on 5 oct. retest after updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;none of this replaces the real boundary.&lt;/strong&gt; the agent's shell shouldn't have a production database url in the first place. run prod migrations from ci, give the app a role that doesn't own the tables if you can, and turn on point-in-time restore or scheduled dumps for anything an agent can reach.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] hooks that guard destructive commands return deny, not ask&lt;/li&gt;
&lt;li&gt;[ ] instructions for the agent go in &lt;code&gt;user_message&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;failClosed: true&lt;/code&gt; on the hook&lt;/li&gt;
&lt;li&gt;[ ] matcher and rules cover your own npm scripts and wrappers&lt;/li&gt;
&lt;li&gt;[ ] tested with piped json payloads, then once for real in your actual run mode&lt;/li&gt;
&lt;li&gt;[ ] no prod &lt;code&gt;DATABASE_URL&lt;/code&gt; in the agent's environment&lt;/li&gt;
&lt;li&gt;[ ] backups or pitr on every database the agent can reach&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Written with AI assistance and checked against Cursor forum topics 173694 and 174902 (including the staff replies), the Cursor hooks documentation, and the drizzle-kit 0.31.11 CLI source. The hook was tested with sample JSON payloads, not inside a live Cursor session.&lt;/p&gt;

</description>
      <category>cursor</category>
      <category>database</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>three site and app shutdowns with deadlines before april. solo, weebly in 67 countries, verizon connect work</title>
      <dc:creator>labyrinthlab</dc:creator>
      <pubDate>Sat, 17 Oct 2026 00:28:43 +0000</pubDate>
      <link>https://dev.to/labyrinthlab/three-site-and-app-shutdowns-with-deadlines-before-april-solo-weebly-in-67-countries-verizon-4c2j</link>
      <guid>https://dev.to/labyrinthlab/three-site-and-app-shutdowns-with-deadlines-before-april-solo-weebly-in-67-countries-verizon-4c2j</guid>
      <description>&lt;p&gt;a few platforms are winding down at the same time this year, and the dates are easy to mix up. here's what each vendor actually says, pulled from their own notices, plus what you should export before the window closes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;updated 23 oct 2026:&lt;/strong&gt; verizon posted final cutoffs for work in its &lt;a href="https://support.work.fleetmatics.com/hc/en-us/articles/51620051793683-Final-Transition-Deadlines-Data-Export-Cutoffs" rel="noopener noreferrer"&gt;final transition deadlines &amp;amp; data export cutoffs&lt;/a&gt; article (updated 22 oct 2026). the verizon section now has all four dates: on 4 jan 2027 the normal screens and the mobile app stop, 28 feb 2027 is the last day to start a new export, and 31 mar 2027 is the last day to download.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;updated 18 oct 2026:&lt;/strong&gt; added short export steps for each one, plus a fourth shutdown that came up since posting: appdrag, which closes on 1 nov 2026.&lt;/p&gt;

&lt;p&gt;one note up front: none of these notices give a time zone. if a date matters to you, treat the day before as your real deadline.&lt;/p&gt;

&lt;h2&gt;
  
  
  solo (soloist.ai)
&lt;/h2&gt;

&lt;p&gt;solo is mozilla's ai website builder, and it's closing for good. from solo's own &lt;a href="https://support.soloist.ai/doc/solo-shutdown-faq" rel="noopener noreferrer"&gt;shutdown faq&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"All Solo websites and data will be deleted on November 30, 2026."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;so 30 nov 2026 is both the export deadline and the deletion date. there's no grace period after it. signups are already closed, and solo says refunds are calculated as of 1 oct 2026.&lt;/p&gt;

&lt;p&gt;the export lives under account settings &amp;gt; download website data. you get a zip with an html export, a csv of image links, and your contact form and newsletter submissions. the catch is that the images themselves aren't in the zip, only links to them. those links die with solo, so download the actual image files before 30 nov, not after.&lt;/p&gt;

&lt;p&gt;also keep the form submissions off any public host. that's your visitors' names and emails.&lt;/p&gt;

&lt;h3&gt;
  
  
  solo export steps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;log in at soloist.ai. if your site isn't there, you're probably on the wrong email (&lt;a href="https://support.soloist.ai/doc/i-dont-see-my-websites" rel="noopener noreferrer"&gt;i don't see my websites&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;click the menu at the top right, then &lt;strong&gt;account settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;download website data&lt;/strong&gt; (&lt;a href="https://support.soloist.ai/doc/solo-shutdown-faq" rel="noopener noreferrer"&gt;shutdown faq&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;unzip it and open the csv. solo says "Image source files are not included in the ZIP", so download each image from the csv links before 30 nov.&lt;/li&gt;
&lt;li&gt;move the form and newsletter submissions out of the site folder before you upload it anywhere.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  weebly, but only in 67 countries
&lt;/h2&gt;

&lt;p&gt;this one gets misreported a lot. weebly is not shutting down everywhere. weebly's notice, "changes to weebly global operations", covers 67 countries. if you're outside those, nothing changes for you. check the notice to see if your country is on the list.&lt;/p&gt;

&lt;p&gt;if you are affected, here's the timeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;new page publishing stopped on 29 jun 2026.&lt;/li&gt;
&lt;li&gt;sites were unpublished on 27 sep 2026, so they're already offline.&lt;/li&gt;
&lt;li&gt;login and export access ends on 26 dec 2026.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;that last date is the one that matters now. your site isn't public anymore, but you can still log in and get your content out until 26 dec. after that, the account is gone. if you've been putting it off because the site "already disappeared", it hasn't fully disappeared yet, so go export it.&lt;/p&gt;

&lt;h3&gt;
  
  
  weebly export steps
&lt;/h3&gt;

&lt;p&gt;there are two exports, and you want both.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;site archive: in the site editor go to &lt;strong&gt;settings &amp;gt; general&lt;/strong&gt;, scroll to &lt;strong&gt;archive&lt;/strong&gt;, type your email and click &lt;strong&gt;email archive&lt;/strong&gt;. the download link arrives by email (&lt;a href="https://www.weebly.com/app/help/us/en/topics/back-up-your-site" rel="noopener noreferrer"&gt;back up your site&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;account data: account settings &amp;gt; &lt;strong&gt;my data&lt;/strong&gt; &amp;gt; &lt;strong&gt;download my data&lt;/strong&gt;. it's a separate file.&lt;/li&gt;
&lt;li&gt;open both right away. blog and store pages aren't in the site archive, so copy blog posts out of the editor while you can still log in.&lt;/li&gt;
&lt;li&gt;if you bought your domain through weebly, transfer it out before 26 dec. the steps are on weebly's &lt;a href="https://www.weebly.com/app/help/us/en/topics/changes-to-weebly-global-operations" rel="noopener noreferrer"&gt;notice&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  verizon connect work
&lt;/h2&gt;

&lt;p&gt;not a website builder, but plenty of small field service businesses run on it, so it belongs on the list. verizon's &lt;a href="https://support.work.fleetmatics.com/hc/en-us/articles/51620051793683-Final-Transition-Deadlines-Data-Export-Cutoffs" rel="noopener noreferrer"&gt;final transition deadlines &amp;amp; data export cutoffs&lt;/a&gt; article now lays out four dates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;31 dec 2026, end of active service. no more scheduling jobs, adding clients or managing active workflows, and verizon says "All end-of-year billing, invoicing, and job closures must be finalized by this date." the comprehensive faq still says: "We recommend completing all exports by December 31, 2026." that's also when they want daily operations moved somewhere else.&lt;/li&gt;
&lt;li&gt;4 jan 2027: "Standard access to the Work platform ceases." admin logins get sent to the data migration tool, and "The Work Mobile App will also cease to function".&lt;/li&gt;
&lt;li&gt;28 feb 2027: "The absolute final day to initiate a new data export job."&lt;/li&gt;
&lt;li&gt;31 mar 2027: "The final day to download previously completed ZIP files." after that, verizon says the data "will be irretrievably purged".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the stretch after 4 jan is a safety net, not a plan. treat 31 dec as the deadline, 28 feb as your last chance to start an export, and 31 mar as the point of no return.&lt;/p&gt;

&lt;h3&gt;
  
  
  verizon connect work export steps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;log in as a full account administrator. only admins get the &lt;strong&gt;migration tool&lt;/strong&gt; button in the top nav (&lt;a href="https://support.work.fleetmatics.com/hc/en-us/articles/53055861646739-Work-Data-Export-Migration-Tool" rel="noopener noreferrer"&gt;verizon's guide&lt;/a&gt;). if nothing opens, allow pop-ups.&lt;/li&gt;
&lt;li&gt;export clients, then the date-range exports (jobs, invoices, quotes and the rest). pick csv if unsure, and save the view schema and er diagram too.&lt;/li&gt;
&lt;li&gt;run jobs with attachments in monthly or quarterly chunks. don't cancel or re-request a slow one, that resets your place in the queue.&lt;/li&gt;
&lt;li&gt;download every monthly zip from the &lt;strong&gt;job history&lt;/strong&gt; page. each one is only available for three weeks.&lt;/li&gt;
&lt;li&gt;bring the files to your next tool yourself. verizon says: "To protect your privacy, we cannot move data directly to a third party."&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  appdrag (added 18 oct 2026)
&lt;/h2&gt;

&lt;p&gt;appdrag is a website, backend and newsletter builder. from its own &lt;a href="https://support.appdrag.com/doc/Sunset-of-AppDrag" rel="noopener noreferrer"&gt;sunset notice&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"we have decided to cease accepting new customers and projects with immediate effect, and to permanently shut down AppDrag on November 1st, 2026."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;annual renewals are already withdrawn. the notice says you need to migrate any websites, domains or newsletters you want to keep, links its own guides for downloading your project and transferring your domain, and says support stays available until the shutdown date. that's the tightest deadline on this list, so start with this one if it's yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  what to do this week, whichever one you're on
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;log in now and confirm you're on the right account. finding out your site was made under an old email is better in october than on deadline day.&lt;/li&gt;
&lt;li&gt;run the export and open it. a zip you never unzipped isn't a backup.&lt;/li&gt;
&lt;li&gt;check for things that live outside the export, like solo's image links.&lt;/li&gt;
&lt;li&gt;keep customer data private and stored somewhere you control.&lt;/li&gt;
&lt;li&gt;put the deadline in your calendar a few days early.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;i keep the dates and their sources in a free tracker here: &lt;a href="https://site-export-rescue.pages.dev/shutdown-tracker/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=three-site-and-app-shutdowns-with-deadlines-before-april-solo-weebly-in-67-countries-verizon-4c2j" rel="noopener noreferrer"&gt;https://site-export-rescue.pages.dev/shutdown-tracker/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=three-site-and-app-shutdowns-with-deadlines-before-april-solo-weebly-in-67-countries-verizon-4c2j&lt;/a&gt; (the data behind it is a cc0 data.json, so reuse it if it helps).&lt;/p&gt;




&lt;p&gt;Written with AI assistance and checked against Solo's Shutdown FAQ (support.soloist.ai), Weebly's "Changes to Weebly global operations" notice (updated 2026-08-13), Verizon Connect Work's Comprehensive FAQ (updated 2026-10-19) and "Final Transition Deadlines &amp;amp; Data Export Cutoffs" (updated 2026-10-22), plus, for the export steps and update, Solo's "I don't see my websites" page, Weebly's "Back up your site" page, Verizon's Data Export Migration Tool guide, and AppDrag's "Sunset of AppDrag" notice (checked 2026-10-17; verizon dates rechecked 2026-10-23).&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>solo (soloist.ai) is shutting down on 30 nov. how to get your site out, images included</title>
      <dc:creator>labyrinthlab</dc:creator>
      <pubDate>Fri, 16 Oct 2026 13:40:49 +0000</pubDate>
      <link>https://dev.to/labyrinthlab/solo-soloistai-is-shutting-down-on-30-nov-how-to-get-your-site-out-images-included-40gp</link>
      <guid>https://dev.to/labyrinthlab/solo-soloistai-is-shutting-down-on-30-nov-how-to-get-your-site-out-images-included-40gp</guid>
      <description>&lt;p&gt;if you built a site with solo, mozilla's ai website builder at soloist.ai, it's closing. the date is in solo's own &lt;a href="https://support.soloist.ai/doc/solo-shutdown-faq" rel="noopener noreferrer"&gt;shutdown faq&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"All Solo websites and data will be deleted on November 30, 2026. There will be no way to recover your site, account, or data after that date."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;so that's the window. the export is quick, but it leaves out one big thing, and that's most of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. log in (the soloist ai login bit)
&lt;/h2&gt;

&lt;p&gt;go to &lt;a href="https://soloist.ai" rel="noopener noreferrer"&gt;soloist.ai&lt;/a&gt; and click sign in. solo lets you log in with your email or your google account (&lt;a href="https://support.soloist.ai/doc/how-to-navigate-the-solo-interface" rel="noopener noreferrer"&gt;solo's interface guide&lt;/a&gt;). you land in the designer, the editor with the toolbar across the top.&lt;/p&gt;

&lt;p&gt;if you log in and your site isn't there, you're probably on the wrong email. solo's &lt;a href="https://support.soloist.ai/doc/i-dont-see-my-websites" rel="noopener noreferrer"&gt;i don't see my websites&lt;/a&gt; page says to check your inbox for mail from @soloist.ai to see which address the site was made with. it also says script blockers like noscript stop solo from loading, so allow soloist.ai if you use one.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. download your website data
&lt;/h2&gt;

&lt;p&gt;the steps from the faq:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;log in to solo.&lt;/li&gt;
&lt;li&gt;click the menu at the top right and select &lt;strong&gt;account settings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;in the account settings popup, click &lt;strong&gt;download website data&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;you get a zip. per solo it has "an HTML export of your website and a CSV file with links to your image assets, as well as your contact form and newsletter submissions."&lt;/p&gt;

&lt;p&gt;unzip it right away and look inside. keep the original zip somewhere safe and work on a copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. the images aren't in the zip
&lt;/h2&gt;

&lt;p&gt;this is the part people miss. solo says it directly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Image source files are not included in the ZIP. Download each image individually from the links in the CSV file before the shutdown date on November 30, 2026."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;so the csv is a list of links, not your photos. once solo deletes everything, those links stop working, and your html points at nothing.&lt;/p&gt;

&lt;p&gt;if you have five images, just open each link and save it. if you have fifty, script it. here's a small python example, standard library only. it's an example to adapt, not a finished tool. i haven't seen every solo export, so check your csv and html before you run it.&lt;/p&gt;

&lt;p&gt;if you'd rather not script it, i put a free browser tool for this here: &lt;a href="https://site-export-rescue.pages.dev/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=solo-soloistai-is-shutting-down-on-30-nov-how-to-get-your-site-out-images-included-40gp" rel="noopener noreferrer"&gt;site-export-rescue.pages.dev&lt;/a&gt;. heads up, it fetches the images through our server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# EXAMPLE ONLY. adapt it to your export, and run it on a COPY of the unzipped folder.
# usage: python3 grab_images.py path/to/the-image-links.csv
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlparse&lt;/span&gt;

&lt;span class="n"&gt;csv_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;images&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# pull every http(s) link out of the csv, whatever the column is called
&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;csv_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https?://[^\s\"&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,]+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;mapping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[^A-Za-z0-9._-]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# short hash prefix so two files both called photo.jpg don't overwrite each other
&lt;/span&gt;    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;
    &lt;span class="n"&gt;dest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAILED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;images/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;

&lt;span class="c1"&gt;# point the html at the local copies
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;images saved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;a few things to watch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;images/...&lt;/code&gt; paths are relative. that's fine if your html files sit next to the &lt;code&gt;images&lt;/code&gt; folder. if some pages are in subfolders, they need &lt;code&gt;../images/&lt;/code&gt; instead.&lt;/li&gt;
&lt;li&gt;the html might reference an image slightly differently from the csv, like with size settings on the end, or &lt;code&gt;&amp;amp;amp;&lt;/code&gt; instead of &lt;code&gt;&amp;amp;&lt;/code&gt;. after running it, search your html for leftover remote image links and fix those by hand.&lt;/li&gt;
&lt;li&gt;anything that prints &lt;code&gt;FAILED&lt;/code&gt;, open the link in your browser and save it manually.&lt;/li&gt;
&lt;li&gt;some image files come back without an extension. if one won't display, check what it actually is and rename it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;prefer the shell? pull the links into a plain text file, one per line, and &lt;code&gt;wget -i links.txt -P images&lt;/code&gt; downloads them all. you still have to rewrite the html references yourself though, which is the fiddly half.&lt;/p&gt;

&lt;p&gt;then double-click &lt;code&gt;index.html&lt;/code&gt; and click around. every image should load with your wifi off.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. keep form submissions private
&lt;/h2&gt;

&lt;p&gt;the same zip has your contact form and newsletter submissions. that's your customers' names, emails and messages. don't put them on a public host. anything you upload to a static host can be downloaded by anyone who finds the path.&lt;/p&gt;

&lt;p&gt;before you upload anything, move the submission files out of the site folder and keep them on your own computer plus one private backup. solo's help also has a separate &lt;a href="https://support.soloist.ai/doc/how-can-i-export-my-newsletter-subscribers-and-contact-form-submissions" rel="noopener noreferrer"&gt;export contacts&lt;/a&gt; option in the designer settings if you want a second copy as csv.&lt;/p&gt;

&lt;p&gt;also, the contact form itself stops working when solo closes. a static host can't receive form posts, so swap it for a &lt;code&gt;mailto:&lt;/code&gt; link or a form service.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. host it for free
&lt;/h2&gt;

&lt;p&gt;the folder with &lt;code&gt;index.html&lt;/code&gt; at the top is your site. upload the contents of that folder, not the folder that contains it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;netlify drop.&lt;/strong&gt; go to &lt;a href="https://app.netlify.com/drop" rel="noopener noreferrer"&gt;app.netlify.com/drop&lt;/a&gt; and drag the folder in. Log in to Netlify first. Drop works without an account, but if you drop without signing in, the site is protected with a temporary password until you claim it. more in &lt;a href="https://docs.netlify.com/start/quickstarts/netlify-drop-quickstart" rel="noopener noreferrer"&gt;netlify's drop quickstart&lt;/a&gt;. they also say deploys under 50mb work best.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;cloudflare pages direct upload.&lt;/strong&gt; in the cloudflare dashboard go to workers &amp;amp; pages, then create application &amp;gt; get started &amp;gt; drag and drop your files. name the project, drop the folder or a zip, deploy. drag and drop allows 1,000 files and 25 mib per file. details in &lt;a href="https://developers.cloudflare.com/pages/get-started/direct-upload/" rel="noopener noreferrer"&gt;cloudflare's direct upload docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;either way you get a free subdomain, and you can add your own domain after.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. move your custom domain
&lt;/h2&gt;

&lt;p&gt;if you bought your domain through solo domains (not ionos), solo is moving those to name.com. the faq steps, roughly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;create a name.com account or log in.&lt;/li&gt;
&lt;li&gt;fill in the registrant, administrative and technical contacts on name.com's account contacts page. the transfer can't go through until these are done.&lt;/li&gt;
&lt;li&gt;copy the account code from the top right of the name.com dashboard.&lt;/li&gt;
&lt;li&gt;email it to &lt;a href="mailto:support@soloist.ai"&gt;support@soloist.ai&lt;/a&gt;. solo transfers the domain and emails you when it's done.&lt;/li&gt;
&lt;li&gt;add billing info at name.com so it renews.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;if the domain is from ionos or another registrar, those steps aren't for you. log in wherever you bought it.&lt;/p&gt;

&lt;p&gt;once you control the dns, add the domain in your host's custom domain settings (&lt;a href="https://docs.netlify.com/domains-https/custom-domains/" rel="noopener noreferrer"&gt;netlify&lt;/a&gt;, &lt;a href="https://developers.cloudflare.com/pages/configuration/custom-domains/" rel="noopener noreferrer"&gt;cloudflare pages&lt;/a&gt;) and copy the records they show you into your dns. leave email (mx) records alone if you use email on that domain. dns changes can take a while to show up.&lt;/p&gt;

&lt;h2&gt;
  
  
  the other option: data liberation agent
&lt;/h2&gt;

&lt;p&gt;automattic, the company behind wordpress.com, has an open source command-line tool called &lt;a href="https://github.com/Automattic/data-liberation-agent" rel="noopener noreferrer"&gt;data liberation agent&lt;/a&gt;. you point it at a url and it captures the live site into plain html, css, media and fonts, with references rewritten so it works locally. its readme lists soloist as a supported platform.&lt;/p&gt;

&lt;p&gt;the catch is in that word live. it reads your published site, so it has to run before 30 nov, and it won't get your form submissions. it's a node project that also needs playwright's chromium installed, so it's best if you're fine in a terminal. if you are, it's worth running alongside the zip export, not instead of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  that's it
&lt;/h2&gt;

&lt;p&gt;download the zip today. get the images out while the links still work. keep the form data off the internet. the hosting and domain stuff can happen any time, but the export can't happen after 30 nov.&lt;/p&gt;




&lt;p&gt;Written with AI assistance and checked against Solo's shutdown FAQ and help pages, the Netlify and Cloudflare docs, and Automattic's Data Liberation Agent README.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>beginners</category>
      <category>python</category>
    </item>
    <item>
      <title>leaving verizon connect work? how to move your clients and job history into your next tool</title>
      <dc:creator>labyrinthlab</dc:creator>
      <pubDate>Sat, 10 Oct 2026 23:38:53 +0000</pubDate>
      <link>https://dev.to/labyrinthlab/leaving-verizon-connect-work-how-to-move-your-clients-and-job-history-into-your-next-tool-1l1c</link>
      <guid>https://dev.to/labyrinthlab/leaving-verizon-connect-work-how-to-move-your-clients-and-job-history-into-your-next-tool-1l1c</guid>
      <description>&lt;p&gt;if you run a field service business on verizon connect work (it used to be called fleetmatics work), it's being retired, and the part that actually matters is getting your clients and job history out and into whatever you use next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;updated 23 oct 2026:&lt;/strong&gt; verizon posted final cutoffs in its &lt;a href="https://support.work.fleetmatics.com/hc/en-us/articles/51620051793683-Final-Transition-Deadlines-Data-Export-Cutoffs" rel="noopener noreferrer"&gt;final transition deadlines &amp;amp; data export cutoffs&lt;/a&gt; article (updated 22 oct 2026), so i fixed the dates below. short version: on 4 jan 2027 the normal work screens and the mobile app stop, 28 feb 2027 is the last day to start a new export, and 31 mar 2027 is the last day to download finished zips.&lt;/p&gt;

&lt;p&gt;the dates, straight from verizon's &lt;a href="https://support.work.fleetmatics.com/hc/en-us/articles/51620051793683-Final-Transition-Deadlines-Data-Export-Cutoffs" rel="noopener noreferrer"&gt;final transition deadlines &amp;amp; data export cutoffs&lt;/a&gt; page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;31 dec 2026&lt;/strong&gt;, "End of Active Service": "You will no longer be able to schedule jobs, add new clients, or manage active workflows." verizon also says "All end-of-year billing, invoicing, and job closures must be finalized by this date."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 jan 2027&lt;/strong&gt;, "Platform UI Restriction": "Standard access to the Work platform ceases." admin logins get redirected to the data migration tool, and "The Work Mobile App will also cease to function".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;28 feb 2027&lt;/strong&gt;, "Data Export Initiation Cutoff": "The absolute final day to initiate a new data export job." big exports can take over a week, so don't wait for that day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;31 mar 2027&lt;/strong&gt;, "Final Infrastructure Decommissioning": "The final day to download previously completed ZIP files." after that, verizon says your job logs, signatures and attachments "will be irretrievably purged".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the &lt;a href="https://support.work.fleetmatics.com/hc/en-us/articles/51912174290451-Comprehensive-FAQ" rel="noopener noreferrer"&gt;comprehensive faq&lt;/a&gt; still says "We recommend completing all exports by December 31, 2026." so that's still the safe target. international customers have their &lt;a href="https://support.work.fleetmatics.com/hc/en-us/articles/52853107607699-Frequently-Asked-Questions-International-Customers" rel="noopener noreferrer"&gt;own faq page&lt;/a&gt;, so check that one if you're outside north america.&lt;/p&gt;

&lt;p&gt;also worth knowing early: verizon won't hand your data to your next provider. their words: "To protect your privacy, we cannot move data directly to a third party." you download it, then you bring it to the new tool.&lt;/p&gt;

&lt;p&gt;one honest caveat: i haven't seen a real export file, and there's no public sample export, so everything below about files and formats is what verizon's docs say. check it against what you actually get.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. who can export, and where
&lt;/h2&gt;

&lt;p&gt;only full account administrators can use the export. in the top nav of work there's a &lt;strong&gt;migration tool&lt;/strong&gt; button, which opens in a new tab or window (&lt;a href="https://support.work.fleetmatics.com/hc/en-us/articles/53055861646739-Work-Data-Export-Migration-Tool" rel="noopener noreferrer"&gt;verizon's guide&lt;/a&gt;). if you click it and nothing happens, your browser is probably blocking it as a pop-up. allow pop-ups for work2.fleetmatics.com and try again. if it loops back to the dashboard, verizon suggests firefox or a private window.&lt;/p&gt;

&lt;p&gt;there's also the older route from before the shutdown: &lt;strong&gt;settings &amp;gt; import/export data &amp;gt; export data&lt;/strong&gt;. it exports clients, suppliers, items and jobs as excel or csv, and jobs can be filtered by job start date, creation date or invoice date (&lt;a href="https://support.work.fleetmatics.com/hc/en-us/articles/4413406917907-How-do-I-export-data-from-Work" rel="noopener noreferrer"&gt;2022 article&lt;/a&gt;, so the menu may differ now).&lt;/p&gt;

&lt;h2&gt;
  
  
  2. what to export
&lt;/h2&gt;

&lt;p&gt;the migration tool splits things in two.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;general exports&lt;/strong&gt; (no dates): clients, and start/finish questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;date-range exports&lt;/strong&gt;: accounts (your inventory and purchases accounts), invoices, jobs (data only), jobs with attachments, purchase orders, quotes, suppliers. each range can be at most 2 years apart, and verizon says not to overlap ranges.&lt;/p&gt;

&lt;p&gt;each category comes as &lt;strong&gt;csv or json&lt;/strong&gt;. verizon says if you're unsure, pick csv, since excel or google sheets can open it.&lt;/p&gt;

&lt;p&gt;two extras people skip: every category has a &lt;strong&gt;view schema&lt;/strong&gt; button, and there's a &lt;strong&gt;show er diagram&lt;/strong&gt; button with a download option. save both. if your next provider isn't one of verizon's partners, verizon says those providers "do not have access to our export data schemas", so the schema and diagram are the closest thing to a map of your data you can give them.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. running it without losing your place
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;text exports are quick. anything with photos, signatures and pdfs can take "several days to over a week".&lt;/li&gt;
&lt;li&gt;don't cancel or re-request a slow one. that resets your spot in the queue. closing the browser doesn't stop it.&lt;/li&gt;
&lt;li&gt;run &lt;strong&gt;jobs with attachments&lt;/strong&gt; in small chunks. verizon's own tip is monthly or quarterly.&lt;/li&gt;
&lt;li&gt;results land on the &lt;strong&gt;job history&lt;/strong&gt; page, grouped into zip files &lt;strong&gt;by month&lt;/strong&gt;. each download is only available for &lt;strong&gt;three weeks&lt;/strong&gt; after it finishes, so watch the "available until" column and grab every month.&lt;/li&gt;
&lt;li&gt;work deletes job records that haven't been modified in 7 years, so anything before about september 2019 is already gone (&lt;a href="https://support.work.fleetmatics.com/hc/en-us/articles/4413406304787-How-long-will-Work-store-my-data" rel="noopener noreferrer"&gt;retention policy&lt;/a&gt;). a range from before then, or one with no completed jobs, gives you a 0-record file.&lt;/li&gt;
&lt;li&gt;if a file comes back blank but you can see the jobs in the jobs tab, verizon suggests smaller chunks (6 months or a year). for export trouble, their faq lists &lt;a href="mailto:work.support@srv.verizonconnect.com"&gt;work.support@srv.verizonconnect.com&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. check the files the same day
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;unzip every month and make sure each one actually has something in it.&lt;/li&gt;
&lt;li&gt;open the clients csv and a couple of job files. do names, addresses and phone numbers look right? pick a busy month and roughly compare the row count to what the jobs tab shows.&lt;/li&gt;
&lt;li&gt;check attachments. verizon says they come in folders numbered for each job, and file names match the job id in the job csv. pick three jobs you remember, find their files and open a photo.&lt;/li&gt;
&lt;li&gt;keep two copies in two places, like a laptop and an external drive.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. a browsable offline copy, by hand
&lt;/h2&gt;

&lt;p&gt;after you move you'll still want to look things up ("what did we do at this address in 2022"). a plain spreadsheet does most of it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;never edit the original files. copy them into a "working" folder and edit the copies.&lt;/li&gt;
&lt;li&gt;import the csv through the spreadsheet's import option instead of double-clicking it, and set postcode, zip and phone columns to text, otherwise leading zeros can disappear.&lt;/li&gt;
&lt;li&gt;one workbook, one sheet each for clients, jobs and invoices. paste each month's jobs under the last, and keep only one header row.&lt;/li&gt;
&lt;li&gt;freeze the header row and turn on filters to find jobs by client or address.&lt;/li&gt;
&lt;li&gt;verizon says jobs link back to the client name, so a lookup (xlookup or vlookup) can pull client details into the jobs sheet.&lt;/li&gt;
&lt;li&gt;add a column with each job's attachment folder path. in desktop excel a hyperlink to a relative folder path can open the folder, as long as the workbook sits next to the unzipped folders. test it on one row first.&lt;/li&gt;
&lt;li&gt;save the workbook as .xlsx, but keep the original csvs and zips too. those are your real backup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;if you'd rather not build the spreadsheet, i put a free browser tool for it here: &lt;a href="https://job-archive-viewer.pages.dev/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=leaving-verizon-connect-work-how-to-move-your-clients-and-job-history-into-your-next-tool-1l1c" rel="noopener noreferrer"&gt;job-archive-viewer.pages.dev&lt;/a&gt;. it turns the export files into a browsable offline archive, and your files stay in your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. what people commonly miss
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;attachments.&lt;/strong&gt; plain "jobs" is data only. photos, pdfs and signatures only come with &lt;strong&gt;jobs with attachments&lt;/strong&gt;, and that's the slow one, so start it first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;notes.&lt;/strong&gt; verizon's docs don't spell out which note fields end up in the job export. open a job you know has notes and check the export for them. if they're missing, copy the important ones while you can still log in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;invoices, quotes, purchase orders, suppliers.&lt;/strong&gt; each is its own export. your accountant will probably want the invoices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;inventory items and price books.&lt;/strong&gt; verizon's &lt;a href="https://support.work.fleetmatics.com/hc/en-us/articles/51909671318675-Exporting-Master-Data-CSV" rel="noopener noreferrer"&gt;master data page&lt;/a&gt; lists them as csv exports, but the migration tool's list doesn't name inventory items, so check settings &amp;gt; import/export data for them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;start/finish questions.&lt;/strong&gt; small, but export them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;client custom fields.&lt;/strong&gt; work supports them. check they show up in the clients export.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;recurring jobs.&lt;/strong&gt; i couldn't find anything saying recurrence patterns export. write down your recurring schedules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;gps.&lt;/strong&gt; if you use reveal, that's a separate platform and keeps running, but verizon says the work/reveal integration stops once you move off work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. planning the move into your next tool
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;before you commit, ask the new tool what it can import (clients, job history, attachments) and in what format.&lt;/li&gt;
&lt;li&gt;give their onboarding people a small sample of your files plus the schema and er diagram from step 2.&lt;/li&gt;
&lt;li&gt;verizon's faq says you can take your exports "to any field service provider of your choice", but mapping the files is then that provider's job.&lt;/li&gt;
&lt;li&gt;move clients first, since jobs hang off clients. then upcoming and recurring work. job history can go in too, or stay in your offline copy if the new tool can't take it.&lt;/li&gt;
&lt;li&gt;verizon's &lt;a href="https://support.work.fleetmatics.com/hc/en-us/articles/51893757118099-Migration-Checklist" rel="noopener noreferrer"&gt;migration checklist&lt;/a&gt; says to train your team on the new tool well before december 31, 2026. run both for a bit if you can.&lt;/li&gt;
&lt;li&gt;keep the original zips untouched after the move.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  that's it
&lt;/h2&gt;

&lt;p&gt;none of this is hard, just slow and easy to put off. queue the attachment exports this week, download each month inside its three-week window, check a few files, and your history goes wherever you do.&lt;/p&gt;




&lt;p&gt;Written with AI assistance and checked against Verizon's published docs.&lt;/p&gt;

</description>
      <category>smallbusiness</category>
      <category>csv</category>
      <category>backup</category>
      <category>migration</category>
    </item>
    <item>
      <title>how to move a weebly site to a free static host before the export window closes</title>
      <dc:creator>labyrinthlab</dc:creator>
      <pubDate>Sat, 10 Oct 2026 19:38:59 +0000</pubDate>
      <link>https://dev.to/labyrinthlab/how-to-move-a-weebly-site-to-a-free-static-host-before-the-export-window-closes-3j2d</link>
      <guid>https://dev.to/labyrinthlab/how-to-move-a-weebly-site-to-a-free-static-host-before-the-export-window-closes-3j2d</guid>
      <description>&lt;p&gt;quick context first, because a lot of posts are getting this wrong. weebly is not closing everywhere. it is winding down service in 67 countries. in those countries sites were unpublished on 27 sep 2026, and login access ends on 26 dec 2026. if you're in the us, uk or eu you're not on the list. the full country list and dates are on &lt;a href="https://www.weebly.com/app/help/us/en/topics/changes-to-weebly-global-operations" rel="noopener noreferrer"&gt;weebly's notice&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;so if you (or a friend, or a client, or your aunt's bakery) had a weebly site in one of those countries, it's offline now and the account still works until 26 dec. that's the window. after that you can't log in to get anything out.&lt;/p&gt;

&lt;p&gt;here's what i'd do, roughly in order.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. get the site archive (and the account data)
&lt;/h2&gt;

&lt;p&gt;the site archive is a zip of your published pages. in the site editor go to &lt;strong&gt;settings &amp;gt; general&lt;/strong&gt;, scroll down to &lt;strong&gt;archive&lt;/strong&gt;, type your email and click &lt;strong&gt;email archive&lt;/strong&gt;. you get an email with a download link. weebly's page for this is &lt;a href="https://www.weebly.com/app/help/us/en/topics/back-up-your-site" rel="noopener noreferrer"&gt;back up your site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;there's also a separate account export: account settings &amp;gt; my data &amp;gt; download my data. it's a different file. grab both, keep them in two places, and actually open the zip right away to check it isn't empty. you won't be able to re-download after 26 dec.&lt;/p&gt;

&lt;p&gt;one thing weebly also says: you can't import the zip back into weebly. it's a backup or a starting point for another host, that's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. know what's not in the zip
&lt;/h2&gt;

&lt;p&gt;straight from weebly's backup page: blog and store pages aren't included, because they live in weebly's database. contact forms and slideshows may not work outside the editor either.&lt;/p&gt;

&lt;p&gt;and something they don't really spell out. the archive doesn't contain weebly's shared stylesheets and fonts. your pages still point at them on weebly's servers (&lt;code&gt;cdn*.editmysite.com&lt;/code&gt;). same for a bunch of scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. what breaks if you just upload it
&lt;/h2&gt;

&lt;p&gt;i unzipped a real 2026 archive and looked at the html. this is what you're dealing with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;http links to weebly's cdn.&lt;/strong&gt; css, fonts and scripts load over plain &lt;code&gt;http://&lt;/code&gt;. any free static host serves your site over https, and browsers block http stylesheets and scripts on https pages (mixed content). result: the page loads unstyled and half the js is dead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;theme script paths don't match.&lt;/strong&gt; the pages ask for &lt;code&gt;files/theme/plugins.js&lt;/code&gt; and &lt;code&gt;files/theme/custom.js&lt;/code&gt;, but in the zip those files sit at &lt;code&gt;files/theme/files/plugins.js&lt;/code&gt; and &lt;code&gt;files/theme/files/custom.js&lt;/code&gt;. so they 404.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;the mobile menu.&lt;/strong&gt; the hamburger menu needs those theme scripts. with the paths broken (or the https issue), it just doesn't open on phones. in my test it only worked after fixing both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;editor and account scripts.&lt;/strong&gt; stuff like weebly's &lt;code&gt;main.js&lt;/code&gt;, a customer accounts script, and inline &lt;code&gt;_W&lt;/code&gt; config blocks. they were written to run on weebly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;the weebly badge / footer.&lt;/strong&gt; if your site had one, it's weebly branding you probably don't want on your own host.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;remote css and fonts.&lt;/strong&gt; even when they load, they're on weebly's servers, which you don't control.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. fix it by hand
&lt;/h2&gt;

&lt;p&gt;work on a copy. open the long-id folder inside the zip, that's your site root, &lt;code&gt;index.html&lt;/code&gt; should be at the top.&lt;/p&gt;

&lt;p&gt;if you'd rather not do this by hand, i put a free browser tool for it here: &lt;a href="https://archive-to-static.pages.dev/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=how-to-move-a-weebly-site-to-a-free-static-host-before-the-export-window-closes-3j2d" rel="noopener noreferrer"&gt;archive-to-static.pages.dev&lt;/a&gt;. it upgrades the links to https and strips weebly's theme scripts, adding its own mobile menu.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the https fix.&lt;/strong&gt; in every &lt;code&gt;.html&lt;/code&gt; file, swap &lt;code&gt;http://cdn&lt;/code&gt; for &lt;code&gt;https://cdn&lt;/code&gt;. with sed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run inside the site folder. on macos use: sed -i '' ...&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s#http://cdn#https://cdn#g'&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;any editor with project-wide find and replace does the same thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the theme path fix.&lt;/strong&gt; do this one yourself and check it, nothing figures it out for you. either move the two files up a folder, or rewrite the paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s#files/theme/plugins.js#files/theme/files/plugins.js#g; s#files/theme/custom.js#files/theme/files/custom.js#g'&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then open the site locally (or on your host, see below), shrink the browser to phone width, and check the menu opens. your archive might differ from mine, so look at what the html actually asks for before you run anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;removing scripts.&lt;/strong&gt; go slow here. delete the badge or footer block if there is one, and scripts that clearly only matter on weebly, like the customer accounts one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- delete lines like this --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"http://cdn11.editmysite.com/js/site/main-customer-accounts-site.js?buildtime=1234"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;reload after each removal. some of weebly's scripts (jquery, &lt;code&gt;main.js&lt;/code&gt;) are what the theme and galleries lean on, so ripping everything out can break more than it fixes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;remote css and fonts.&lt;/strong&gt; if you want the site to not depend on weebly's servers at all, download each &lt;code&gt;editmysite.com&lt;/code&gt; stylesheet and font css into your folder, then grab the font files those css files reference, and point the &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tags at your local copies. it's tedious. but it's the only way to be sure the look survives whatever weebly does with those servers later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the contact form.&lt;/strong&gt; it posts to weebly, so it's dead on a new host. replace it with a &lt;code&gt;mailto:&lt;/code&gt; link or a form service. export your old form entries from the dashboard first.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. save blog posts separately
&lt;/h2&gt;

&lt;p&gt;since blog posts aren't in the zip:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;check the download my data file. weebly doesn't say what's in it, so open it and look for your post text.&lt;/li&gt;
&lt;li&gt;while you can still log in, copy each post out of the editor into plain html or markdown. boring, but reliable. save the images too.&lt;/li&gt;
&lt;li&gt;if you missed some, the &lt;a href="https://web.archive.org/" rel="noopener noreferrer"&gt;wayback machine&lt;/a&gt; may have captures of your old blog pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;store items are a separate thing again. there's a csv export in the dashboard, but the csv only links to product images on weebly, so download the images too.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. host it for free
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;netlify drop.&lt;/strong&gt; go to &lt;a href="https://app.netlify.com/drop" rel="noopener noreferrer"&gt;netlify drop&lt;/a&gt; and drag your site folder in. log in to netlify first. drop works without an account, but if you drop without signing in, the site is protected with a temporary password until you claim it. details in &lt;a href="https://docs.netlify.com/start/quickstarts/netlify-drop-quickstart" rel="noopener noreferrer"&gt;netlify's drop quickstart&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;cloudflare pages direct upload.&lt;/strong&gt; in the cloudflare dashboard: workers &amp;amp; pages &amp;gt; create application &amp;gt; get started &amp;gt; drag and drop your files, name the project, deploy. limits are 1,000 files and 25 MiB per file for drag and drop. see &lt;a href="https://developers.cloudflare.com/pages/get-started/direct-upload/" rel="noopener noreferrer"&gt;cloudflare's direct upload docs&lt;/a&gt;. if the archive has old junk like &lt;code&gt;.swf&lt;/code&gt; files you don't use, delete them first.&lt;/p&gt;

&lt;p&gt;keep your old file names (&lt;code&gt;about.html&lt;/code&gt;, &lt;code&gt;contact.html&lt;/code&gt;) so existing links still work.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. move the custom domain
&lt;/h2&gt;

&lt;p&gt;if you bought the domain through weebly, transfer it out before 26 dec. dashboard &amp;gt; websites &amp;gt; domains &amp;gt; manage &amp;gt; manage domain, turn off registrar lock, copy the epp code, start the transfer at your new registrar. don't change the domain's contact details before transferring, weebly says that triggers a 60 day lock. unlocking also turns off whois privacy, so turn it back on at the new place. country-code domains (like .de) and google workspace go through weebly support. all of this is on the &lt;a href="https://www.weebly.com/app/help/us/en/topics/changes-to-weebly-global-operations" rel="noopener noreferrer"&gt;notice&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;if the domain was bought elsewhere, you just repoint it. add it in your host's custom domain settings (&lt;a href="https://docs.netlify.com/domains-https/custom-domains/" rel="noopener noreferrer"&gt;netlify&lt;/a&gt;, &lt;a href="https://developers.cloudflare.com/pages/configuration/custom-domains/" rel="noopener noreferrer"&gt;cloudflare pages&lt;/a&gt;) and copy the dns records they give you into your registrar. remove old records that still point at weebly.&lt;/p&gt;

&lt;p&gt;dns can take a while to update, up to 48 hours. but in some reported cases the old weebly site keeps showing on the domain even after the records are right. if that happens to you, contact weebly support, since it may be something on their end.&lt;/p&gt;

&lt;h2&gt;
  
  
  that's it
&lt;/h2&gt;

&lt;p&gt;nothing here is clever. it's a zip, two find-and-replaces, some careful script removal, and a drag and drop. the deadline is the real problem, so get the archive downloaded today even if you fix it next month.&lt;/p&gt;




&lt;p&gt;Written with AI assistance and checked against the Weebly, Netlify and Cloudflare docs.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Stop Cursor from running drizzle-kit push --force on a real database</title>
      <dc:creator>labyrinthlab</dc:creator>
      <pubDate>Thu, 08 Oct 2026 18:02:52 +0000</pubDate>
      <link>https://dev.to/labyrinthlab/stop-cursor-from-running-drizzle-kit-push-force-on-a-real-database-2mg7</link>
      <guid>https://dev.to/labyrinthlab/stop-cursor-from-running-drizzle-kit-push-force-on-a-real-database-2mg7</guid>
      <description>&lt;p&gt;Here is the failure mode.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You change a Drizzle schema and ask the Cursor agent to sync the database.&lt;/li&gt;
&lt;li&gt;It runs &lt;code&gt;npx drizzle-kit push&lt;/code&gt;. The command stops on a prompt, or errors because there is no terminal to prompt in.&lt;/li&gt;
&lt;li&gt;The agent retries with &lt;code&gt;npx drizzle-kit push --force&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It finishes. A table you cared about is now empty, or a renamed column lost its data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This post covers what &lt;code&gt;push&lt;/code&gt; and &lt;code&gt;--force&lt;/code&gt; actually do (checked against the source of drizzle-kit 0.31.11, the current stable release), why agents end up adding the flag, and guards you can add today, including a free Cursor rule and a hook that blocks the command.&lt;/p&gt;

&lt;h2&gt;
  
  
  What push does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;drizzle-kit push&lt;/code&gt; reads your schema, introspects the live database, diffs the two and applies the SQL directly. No migration file is written. The docs recommend it for rapid prototyping, and against a local throwaway database it's fine.&lt;/p&gt;

&lt;p&gt;Before applying anything, push looks for statements that lose data. On Postgres it counts rows and flags these when existing data is affected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dropping a table, column, schema or materialized view&lt;/li&gt;
&lt;li&gt;changing a column's type&lt;/li&gt;
&lt;li&gt;adding a &lt;code&gt;NOT NULL&lt;/code&gt; column with no default&lt;/li&gt;
&lt;li&gt;dropping a primary key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the type change and the &lt;code&gt;NOT NULL&lt;/code&gt; column, push doesn't only warn. It adds &lt;code&gt;truncate table "&amp;lt;name&amp;gt;" cascade;&lt;/code&gt; to the statements it will run. &lt;code&gt;cascade&lt;/code&gt; also empties every table with a foreign key pointing at that one.&lt;/p&gt;

&lt;p&gt;When anything is flagged, push prints "Found data-loss statements", then "THIS ACTION WILL CAUSE DATA LOSS AND CANNOT BE REVERTED", and asks "Do you still want to push changes?" with "No, abort" as the first option.&lt;/p&gt;

&lt;p&gt;That prompt is the safety net. &lt;code&gt;--force&lt;/code&gt; removes it. The CLI's own description: "Auto-approve all data loss statements. Note: Data loss statements may truncate your tables and data."&lt;/p&gt;

&lt;h2&gt;
  
  
  Renames turn into drops
&lt;/h2&gt;

&lt;p&gt;push can't tell a rename from a drop plus an add. When one column disappears and another appears in the same table, it asks: "Is &lt;code&gt;display_name&lt;/code&gt; column in &lt;code&gt;users&lt;/code&gt; table created or renamed from another column?" The first option is "created". Pick it and the old column, with its data, gets dropped.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--force&lt;/code&gt; doesn't answer that question. The rename prompt runs first. But once "created" is picked, &lt;code&gt;--force&lt;/code&gt; approves the resulting drop with no second look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why agents reach for --force
&lt;/h2&gt;

&lt;p&gt;These prompts are arrow-key menus. An agent running shell commands usually can't drive them. Since drizzle-kit 0.31.10, the prompt library refuses to run without a TTY and throws: "Interactive prompts require a TTY terminal (...). This can happen when running in CI, piped input, or non-interactive shells."&lt;/p&gt;

&lt;p&gt;So plain &lt;code&gt;push&lt;/code&gt; fails, and that failure is safe: nothing was applied. To an agent whose job is to make the command succeed, the obvious fix is the flag that skips the prompt. &lt;code&gt;--force&lt;/code&gt; skips exactly the data-loss prompt, the one that mattered.&lt;/p&gt;

&lt;p&gt;Two details make it worse:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--force&lt;/code&gt; also overrides &lt;code&gt;strict&lt;/code&gt;. In the source, the strict prompt only runs &lt;code&gt;if (!force &amp;amp;&amp;amp; strict)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--verbose&lt;/code&gt; prints the SQL first, but with &lt;code&gt;--force&lt;/code&gt; there's no pause between printing and running.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What drizzle-kit already gives you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The data-loss prompt&lt;/strong&gt;, as long as nobody passes &lt;code&gt;--force&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--strict&lt;/code&gt;&lt;/strong&gt;, or &lt;code&gt;strict: true&lt;/code&gt; in &lt;code&gt;drizzle.config.ts&lt;/code&gt;: asks before running any push, even one with no data loss. It's in the 0.31.x CLI and config type, though the current config docs page doesn't list it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--verbose&lt;/code&gt;&lt;/strong&gt;, or &lt;code&gt;verbose: true&lt;/code&gt;: prints every statement before executing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--explain&lt;/code&gt;&lt;/strong&gt;: prints the planned SQL without applying it. It's on the current push docs page and in the 1.0 release candidate, but not in 0.31.11, so check your version before relying on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;generate&lt;/code&gt; + &lt;code&gt;migrate&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;drizzle-kit generate&lt;/code&gt; writes SQL to your migrations folder, you read it, and &lt;code&gt;drizzle-kit migrate&lt;/code&gt; applies it and records it in the &lt;code&gt;__drizzle_migrations&lt;/code&gt; table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For anything shared (staging, prod, a teammate's database), use &lt;code&gt;generate&lt;/code&gt; + &lt;code&gt;migrate&lt;/code&gt;. A destructive statement in a committed &lt;code&gt;.sql&lt;/code&gt; file shows up in review. The same statement inside a forced push only shows up in terminal scrollback.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;generate&lt;/code&gt; asks the same rename question, so the agent can hit the same TTY error there. That's fine. The right move is to hand that one prompt to you.&lt;/p&gt;

&lt;p&gt;The 1.0 release candidate (rc.4) changes this for agents: in non-interactive mode, &lt;code&gt;push&lt;/code&gt; and &lt;code&gt;generate&lt;/code&gt; stop prompting and report unresolved renames and data-loss confirmations as &lt;code&gt;missing_hints&lt;/code&gt;, which the caller has to resolve explicitly with &lt;code&gt;--hints&lt;/code&gt;. Until that ships as stable, plan for 0.31.x behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guard 1: a rule that makes the agent stop and ask
&lt;/h2&gt;

&lt;p&gt;Save this as &lt;code&gt;.cursor/rules/drizzle-push-safety.mdc&lt;/code&gt;. It's free, written for this post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Drizzle&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;push&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;safety.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Stop&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ask&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;before&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;any&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;drizzle-kit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;command&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;that&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;could&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;lose&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;data."&lt;/span&gt;
&lt;span class="na"&gt;alwaysApply&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Drizzle push safety&lt;/span&gt;

&lt;span class="gu"&gt;## Never run without asking first&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`drizzle-kit push --force`&lt;/span&gt;, including through an npm script
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`drizzle-kit push`&lt;/span&gt; against anything that is not a local throwaway database
&lt;span class="p"&gt;-&lt;/span&gt; Answering a drizzle-kit rename or data-loss prompt on the user's behalf

&lt;span class="gu"&gt;## Before any drizzle-kit command&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Open &lt;span class="sb"&gt;`drizzle.config.*`&lt;/span&gt; and find &lt;span class="sb"&gt;`dbCredentials`&lt;/span&gt;. If it reads an env var,
   name the variable and the file that sets it. Print host and database name,
   never the password.
&lt;span class="p"&gt;2.&lt;/span&gt; If the host is not localhost or a known dev database, stop and ask.

&lt;span class="gu"&gt;## If push stops on a prompt or a TTY error&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Do not add &lt;span class="sb"&gt;`--force`&lt;/span&gt;. Do not pipe input into the prompt.
&lt;span class="p"&gt;-&lt;/span&gt; Quote the prompt to the user: rename question, data-loss list,
  or unique-constraint truncate question.
&lt;span class="p"&gt;-&lt;/span&gt; Offer the tracked path:
&lt;span class="p"&gt;  1.&lt;/span&gt; &lt;span class="sb"&gt;`drizzle-kit generate`&lt;/span&gt;
&lt;span class="p"&gt;  2.&lt;/span&gt; Show the new .sql file. Point out DROP, TRUNCATE,
     ALTER COLUMN ... TYPE and ADD COLUMN ... NOT NULL without DEFAULT.
&lt;span class="p"&gt;  3.&lt;/span&gt; Wait for approval, then &lt;span class="sb"&gt;`drizzle-kit migrate`&lt;/span&gt;.
&lt;span class="p"&gt;-&lt;/span&gt; If &lt;span class="sb"&gt;`generate`&lt;/span&gt; asks the rename question, ask the user to run it
  in their own terminal.

&lt;span class="gu"&gt;## Renames&lt;/span&gt;
If a column or table was renamed in the schema, say so before running
anything. Answering "created" to drizzle-kit's rename question drops
the old column and its data.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;alwaysApply: true&lt;/code&gt; matters here. The agent decides to run push from a shell, often with no Drizzle file in context, so a glob-scoped rule might not be loaded at that moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guard 2: a hook that blocks the command
&lt;/h2&gt;

&lt;p&gt;A rule is text in the prompt, and a model can still talk itself past it. A Cursor hook runs outside the model. Project hooks live in &lt;code&gt;.cursor/hooks.json&lt;/code&gt;, and a &lt;code&gt;beforeShellExecution&lt;/code&gt; hook runs before each shell command the agent wants to execute. Its &lt;code&gt;matcher&lt;/code&gt; is a regex tested against the full command string, so the script only runs on matches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"beforeShellExecution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".cursor/hooks/block-drizzle-force.sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"drizzle-kit push.*--force"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"failClosed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# .cursor/hooks/block-drizzle-force.sh&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "permission": "deny",
  "user_message": "Blocked drizzle-kit push --force. Run it in your own terminal if you mean it.",
  "agent_message": "drizzle-kit push --force is blocked in this repo. Do not retry with other flags. Run drizzle-kit generate, show the SQL, and wait for approval."
}
&lt;/span&gt;&lt;span class="no"&gt;JSON
&lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;chmod +x .cursor/hooks/block-drizzle-force.sh&lt;/code&gt;. Details from the hooks docs worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exit code 2 also blocks, the same as returning &lt;code&gt;"permission": "deny"&lt;/code&gt;. A script that is just &lt;code&gt;cat &amp;gt; /dev/null; exit 2&lt;/code&gt; works if you don't need the messages.&lt;/li&gt;
&lt;li&gt;Other non-zero exits, crashes and timeouts fail open by default. &lt;code&gt;failClosed: true&lt;/code&gt; makes them block instead.&lt;/li&gt;
&lt;li&gt;Project hooks only run in a trusted workspace.&lt;/li&gt;
&lt;li&gt;The matcher only sees the literal command. If push hides behind &lt;code&gt;npm run db:push -- --force&lt;/code&gt;, add &lt;code&gt;|db:push.*--force&lt;/code&gt; to the regex.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you do want a forced push against your local database, run it yourself.&lt;/p&gt;

&lt;p&gt;if you'd rather not wire this up yourself, i put a free copy-paste hook (hooks.json plus the script) here: &lt;a href="https://site-export-rescue.pages.dev/cursor-prisma-drizzle-guard/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=stop-cursor-from-running-drizzle-kit-push-force-on-a-real-database-2mg7" rel="noopener noreferrer"&gt;site-export-rescue.pages.dev/cursor-prisma-drizzle-guard&lt;/a&gt;. it steers the agent's shell commands, not commands you run yourself. it blocks &lt;code&gt;drizzle-kit push --force&lt;/code&gt; (and &lt;code&gt;prisma migrate reset&lt;/code&gt;) unless it can see that the database url is local (localhost, 127.0.0.1, file or sqlite), and the page says what it doesn't catch, like &lt;code&gt;drizzle-kit push&lt;/code&gt; without a force flag or &lt;code&gt;npm run&lt;/code&gt; scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guard 0: credentials
&lt;/h2&gt;

&lt;p&gt;The guard nothing can argue with is still the connection string. If &lt;code&gt;dbCredentials.url&lt;/code&gt; reads &lt;code&gt;process.env.DATABASE_URL&lt;/code&gt; and the &lt;code&gt;.env&lt;/code&gt; your editor loads points at a shared database, the guards above are all that stands between the agent and that data. I covered that, and the Prisma version of this problem, in &lt;a href="https://dev.to/labyrinthlab/stop-cursor-from-running-prisma-migrate-reset-on-the-wrong-database-3pch"&gt;Stop Cursor from running prisma migrate reset on the wrong database&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Written with AI assistance and checked against the Drizzle and Cursor docs.&lt;/p&gt;

</description>
      <category>cursor</category>
      <category>typescript</category>
      <category>drizzle</category>
      <category>database</category>
    </item>
    <item>
      <title>Stop Cursor from running prisma migrate reset on the wrong database</title>
      <dc:creator>labyrinthlab</dc:creator>
      <pubDate>Thu, 08 Oct 2026 13:15:03 +0000</pubDate>
      <link>https://dev.to/labyrinthlab/stop-cursor-from-running-prisma-migrate-reset-on-the-wrong-database-3pch</link>
      <guid>https://dev.to/labyrinthlab/stop-cursor-from-running-prisma-migrate-reset-on-the-wrong-database-3pch</guid>
      <description>&lt;p&gt;Here is the failure mode, step by step.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your migration history drifts. Someone edited an applied migration, or you ran &lt;code&gt;db push&lt;/code&gt; to try something.&lt;/li&gt;
&lt;li&gt;You ask the Cursor agent to fix it. It runs &lt;code&gt;prisma migrate dev&lt;/code&gt;, which reports drift and says the database needs a reset.&lt;/li&gt;
&lt;li&gt;The agent can't answer an interactive prompt, so it runs &lt;code&gt;prisma migrate reset --force&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Your &lt;code&gt;.env&lt;/code&gt; still has the staging or production &lt;code&gt;DATABASE_URL&lt;/code&gt; you pasted in last week to debug something.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;migrate reset&lt;/code&gt; drops the database (or schema), recreates it and applies every migration. On Prisma 6 and earlier it also runs your seed. On dev that's a fine way to get unstuck. Anywhere else it's a restore-from-backup afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why agents walk into this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The error message suggests it.&lt;/strong&gt; Prisma says a reset is needed. An agent trying to make the command succeed takes that literally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agents can't see intent, only the connection string.&lt;/strong&gt; Nothing in &lt;code&gt;postgresql://app@db.internal:5432/app&lt;/code&gt; says "this one has customers in it."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-interactive shells push toward &lt;code&gt;--force&lt;/code&gt;.&lt;/strong&gt; The confirmation prompt that would have saved you is the first thing that gets skipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Prisma already does
&lt;/h2&gt;

&lt;p&gt;Since Prisma ORM 6.15.0, the CLI looks for environment variables that coding agents set (for Cursor, &lt;code&gt;CURSOR_AGENT&lt;/code&gt;). When it finds one, &lt;code&gt;prisma migrate reset&lt;/code&gt; stops with an error telling the agent to explain what it was about to do and ask you first. The agent can only rerun it with &lt;code&gt;PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION&lt;/code&gt; set to the exact text of your consent message. The same check covers &lt;code&gt;prisma db push --force-reset&lt;/code&gt;, and since 7.9.0, &lt;code&gt;prisma db push --accept-data-loss&lt;/code&gt;. That is a good backstop. It still leaves gaps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The agent asks "ok to reset?" and you say yes without checking which database the URL points at.&lt;/li&gt;
&lt;li&gt;It doesn't cover raw SQL, &lt;code&gt;db execute&lt;/code&gt;, or Drizzle.&lt;/li&gt;
&lt;li&gt;It doesn't make the agent read the generated SQL before &lt;code&gt;migrate deploy&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You want the agent checking the target before it reaches that prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: keep production credentials out of reach
&lt;/h2&gt;

&lt;p&gt;The cheapest fix isn't a rule. Don't keep prod or staging URLs in the &lt;code&gt;.env&lt;/code&gt; your editor loads, and give your day-to-day role no &lt;code&gt;DROP&lt;/code&gt; rights on shared databases. Rules steer the agent. Missing credentials can't be talked around.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: a project rule
&lt;/h2&gt;

&lt;p&gt;Cursor loads project rules from &lt;code&gt;.cursor/rules/*.mdc&lt;/code&gt;. Each file is markdown with a small frontmatter block:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt;: what the rule is for.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;globs&lt;/code&gt;: file patterns that pull the rule in.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;alwaysApply&lt;/code&gt;: &lt;code&gt;true&lt;/code&gt; attaches it to every agent request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .cursor/rules
&lt;span class="c"&gt;# save the rule below as .cursor/rules/migration-checklist.mdc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open Customize in the sidebar, go to Rules, and confirm it's listed. Test with "migrations are out of sync, reset the db." A working setup asks about the target database instead of running a command.&lt;/p&gt;

&lt;p&gt;Here is the full rule. Copy it as is or trim what you don't need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pre-push&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pre-migrate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;checklist&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;database&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;schema&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;changes"&lt;/span&gt;
&lt;span class="na"&gt;globs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**/migrations/**"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**/prisma/migrations/**"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**/drizzle/**"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;alwaysApply&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Migration Checklist&lt;/span&gt;

&lt;span class="gu"&gt;## PRE-MIGRATION CHECKLIST&lt;/span&gt;

Before running any migration command, verify:

&lt;span class="gu"&gt;### 1. Environment Check&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Which database am I targeting?**&lt;/span&gt; (dev/staging/prod)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Is this the correct connection string?**&lt;/span&gt; Check &lt;span class="sb"&gt;`DATABASE_URL`&lt;/span&gt; or config
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Do I have a backup?**&lt;/span&gt; (Required for staging/prod)

&lt;span class="gu"&gt;### 2. Schema Review&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Did I read the generated SQL?**&lt;/span&gt; (Not just the schema diff)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Are there destructive operations?**&lt;/span&gt; (DROP, TRUNCATE, DELETE)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Will existing data survive?**&lt;/span&gt; (Type changes, NOT NULL additions)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Are indexes appropriate?**&lt;/span&gt; (Not missing, not excessive)

&lt;span class="gu"&gt;### 3. Data Considerations&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Will this migration lock tables?**&lt;/span&gt; (Large tables = long locks)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Is there a data migration needed?**&lt;/span&gt; (Backfill, transform)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**What's the rollback plan?**&lt;/span&gt; (Reverse migration or restore)

&lt;span class="gu"&gt;## PRE-PUSH CHECKLIST&lt;/span&gt;

Before pushing migration files to the repository:

&lt;span class="gu"&gt;### 1. File Hygiene&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Migration file is committed**&lt;/span&gt; (Not in .gitignore)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Migration name is descriptive**&lt;/span&gt; (Not "migration_1" or "fix")
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**No sensitive data in migration**&lt;/span&gt; (No hardcoded credentials)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**SQL is reviewed and correct**&lt;/span&gt; (Read the actual file)

&lt;span class="gu"&gt;### 2. Local Verification&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Migration applies cleanly locally**&lt;/span&gt; (Tested on fresh DB)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**App still works after migration**&lt;/span&gt; (Run tests)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Migration is idempotent or guarded**&lt;/span&gt; (Won't fail if run twice)

&lt;span class="gu"&gt;### 3. Team Coordination&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**No conflicting migrations from teammates**&lt;/span&gt; (Pull latest first)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Migration order is correct**&lt;/span&gt; (Timestamp/sequence is right)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Breaking changes documented**&lt;/span&gt; (If API changes needed)

&lt;span class="gu"&gt;## PRODUCTION DEPLOYMENT CHECKLIST&lt;/span&gt;

Before deploying migrations to production:

&lt;span class="gu"&gt;### 1. Pre-Deploy&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Database backup completed**&lt;/span&gt; (Verified, not just scheduled)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Maintenance window scheduled**&lt;/span&gt; (If needed for locks)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Rollback plan documented**&lt;/span&gt; (Restore steps or reverse migration)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Team notified**&lt;/span&gt; (On-call aware of deployment)

&lt;span class="gu"&gt;### 2. During Deploy&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Monitor migration progress**&lt;/span&gt; (Watch for locks, errors)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Check application health**&lt;/span&gt; (Errors, latency spikes)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Have rollback ready**&lt;/span&gt; (Don't walk away mid-migration)

&lt;span class="gu"&gt;### 3. Post-Deploy&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Verify migration status**&lt;/span&gt; (&lt;span class="sb"&gt;`migrate status`&lt;/span&gt; shows clean)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Test affected features**&lt;/span&gt; (Not just "app starts")
&lt;span class="p"&gt;-&lt;/span&gt; [ ] &lt;span class="gs"&gt;**Monitor for delayed issues**&lt;/span&gt; (Slow queries, missing data)

&lt;span class="gu"&gt;## DESTRUCTIVE OPERATION ESCALATION&lt;/span&gt;

When a migration contains destructive SQL:

&lt;span class="gu"&gt;### Level 1: Column Drop&lt;/span&gt;
&lt;span class="gs"&gt;**Required:**&lt;/span&gt; Read SQL twice, confirm column name, verify no app references.
&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;
&lt;/span&gt;Migration contains: ALTER TABLE "users" DROP COLUMN "legacy_field"
Confirm: "I verified no code references legacy_field. Drop it."
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gu"&gt;### Level 2: Table Drop&lt;/span&gt;
&lt;span class="gs"&gt;**Required:**&lt;/span&gt; Level 1 + verify no foreign keys, confirm data is backed up or worthless.
&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;
&lt;/span&gt;Migration contains: DROP TABLE "temp_imports"
Confirm: "I verified temp_imports has no important data and no references. Drop it."
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gu"&gt;### Level 3: Data Deletion&lt;/span&gt;
&lt;span class="gs"&gt;**Required:**&lt;/span&gt; Level 2 + explicit row count awareness, backup verification.
&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;
&lt;/span&gt;Migration contains: DELETE FROM "audit_logs" WHERE created_at &amp;lt; '2023-01-01'
Confirm: "I understand this deletes ~50,000 rows. Backup verified. Proceed."
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gu"&gt;### Level 4: Full Reset&lt;/span&gt;
&lt;span class="gs"&gt;**Required:**&lt;/span&gt; All above + explicit database name confirmation.
&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;
&lt;/span&gt;Command: prisma migrate reset / drizzle-kit push --force
Confirm: Type the database name to confirm: ___________
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gu"&gt;## COMMON MISTAKES TO CATCH&lt;/span&gt;

&lt;span class="gu"&gt;### Prisma-Specific&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`migrate dev`&lt;/span&gt; on non-dev database
&lt;span class="p"&gt;-&lt;/span&gt; Editing migration SQL after it's been applied elsewhere
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`@@map`&lt;/span&gt; or &lt;span class="sb"&gt;`@map`&lt;/span&gt; changes that rename without data migration
&lt;span class="p"&gt;-&lt;/span&gt; Enum changes that remove values still in use

&lt;span class="gu"&gt;### Drizzle-Specific&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`push`&lt;/span&gt; instead of &lt;span class="sb"&gt;`generate`&lt;/span&gt; + &lt;span class="sb"&gt;`migrate`&lt;/span&gt; on persistent DB
&lt;span class="p"&gt;-&lt;/span&gt; Missing &lt;span class="sb"&gt;`NOT NULL`&lt;/span&gt; default for existing rows
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`drizzle-kit drop`&lt;/span&gt; on migration that's already deployed elsewhere
&lt;span class="p"&gt;-&lt;/span&gt; Introspect overwriting intentional schema divergence

&lt;span class="gu"&gt;### Universal&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Changing column type without considering data truncation
&lt;span class="p"&gt;-&lt;/span&gt; Adding unique constraint to column with duplicate data
&lt;span class="p"&gt;-&lt;/span&gt; Removing column that's still referenced in app code
&lt;span class="p"&gt;-&lt;/span&gt; Deploying migration before code that handles new schema

&lt;span class="gu"&gt;## ROLLBACK REFERENCE&lt;/span&gt;

&lt;span class="gu"&gt;### Prisma Rollback Options&lt;/span&gt;
&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;bash
&lt;/span&gt;&lt;span class="c"&gt;# Mark migration as rolled back (doesn't undo DB changes)&lt;/span&gt;
prisma migrate resolve &lt;span class="nt"&gt;--rolled-back&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;migration_name]

&lt;span class="c"&gt;# Actual rollback requires manual SQL or restore&lt;/span&gt;
pg_restore &lt;span class="nt"&gt;-d&lt;/span&gt; mydb backup.dump
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gu"&gt;### Drizzle Rollback Options&lt;/span&gt;
&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;bash
&lt;/span&gt;&lt;span class="c"&gt;# No built-in rollback: write reverse migration&lt;/span&gt;
drizzle-kit generate  &lt;span class="c"&gt;# Create new migration to undo&lt;/span&gt;

&lt;span class="c"&gt;# Or restore from backup&lt;/span&gt;
pg_restore &lt;span class="nt"&gt;-d&lt;/span&gt; mydb backup.dump
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gu"&gt;## QUICK COMMANDS REFERENCE&lt;/span&gt;

&lt;span class="gu"&gt;### Prisma&lt;/span&gt;
&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;bash
&lt;/span&gt;prisma migrate status          &lt;span class="c"&gt;# Check pending migrations&lt;/span&gt;
prisma migrate dev             &lt;span class="c"&gt;# Dev: generate + apply&lt;/span&gt;
prisma migrate deploy          &lt;span class="c"&gt;# Prod: apply pending only&lt;/span&gt;
prisma migrate diff            &lt;span class="c"&gt;# Preview changes&lt;/span&gt;
prisma migrate resolve         &lt;span class="c"&gt;# Fix migration state&lt;/span&gt;
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gu"&gt;### Drizzle&lt;/span&gt;
&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;bash
&lt;/span&gt;drizzle-kit status             &lt;span class="c"&gt;# Check state&lt;/span&gt;
drizzle-kit generate           &lt;span class="c"&gt;# Generate SQL files&lt;/span&gt;
drizzle-kit migrate            &lt;span class="c"&gt;# Apply migrations&lt;/span&gt;
drizzle-kit push               &lt;span class="c"&gt;# Direct push (dev only)&lt;/span&gt;
drizzle-kit introspect         &lt;span class="c"&gt;# Generate schema from DB&lt;/span&gt;
&lt;span class="p"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The part doing the work for &lt;code&gt;migrate reset&lt;/code&gt; is &lt;strong&gt;Level 4: Full Reset&lt;/strong&gt;: the agent has to name the database first. The rest keeps it reading generated SQL instead of trusting the schema diff, which is where dropped columns hide. Commit &lt;code&gt;.cursor/rules/&lt;/code&gt; so the whole team gets it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drizzle has the same trap
&lt;/h2&gt;

&lt;p&gt;With Drizzle the risky command is usually &lt;code&gt;drizzle-kit push&lt;/code&gt;. It applies your schema straight to the database with no migration file, and &lt;code&gt;--force&lt;/code&gt; auto-approves data-loss statements. That's fine against a throwaway local database. Against anything persistent, use the tracked flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;drizzle-kit generate   &lt;span class="c"&gt;# writes SQL to your migrations folder&lt;/span&gt;
&lt;span class="c"&gt;# read the SQL&lt;/span&gt;
drizzle-kit migrate    &lt;span class="c"&gt;# applies it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also check &lt;code&gt;dbCredentials.url&lt;/code&gt; in &lt;code&gt;drizzle.config.ts&lt;/code&gt;. If it reads &lt;code&gt;process.env.DATABASE_URL&lt;/code&gt;, it has the same "which &lt;code&gt;.env&lt;/code&gt; is loaded?" problem. The checklist above already matches &lt;code&gt;**/drizzle/**&lt;/code&gt; and flags &lt;code&gt;push&lt;/code&gt; on a persistent database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3, optional: a hard stop
&lt;/h2&gt;

&lt;p&gt;A rule is prose, so a determined agent can still ignore it. For a hard stop, add a Cursor hook. Project hooks live in &lt;code&gt;.cursor/hooks.json&lt;/code&gt;, and a &lt;code&gt;beforeShellExecution&lt;/code&gt; hook runs before each shell command the agent wants to execute. Its &lt;code&gt;matcher&lt;/code&gt; is a regex tested against the full command string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"beforeShellExecution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".cursor/hooks/block-destructive.sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"migrate reset|--force-reset|--accept-data-loss|drizzle-kit push.*--force"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script only runs for matching commands, so it can simply refuse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# .cursor/hooks/block-destructive.sh&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it executable with &lt;code&gt;chmod +x .cursor/hooks/block-destructive.sh&lt;/code&gt;. Exit code 2 blocks the command, the same as returning &lt;code&gt;"permission": "deny"&lt;/code&gt; in the hook's JSON output. Other non-zero exit codes fail open and let the command through, and project hooks only run in a trusted workspace. Run those commands yourself, in your own terminal, when you mean it.&lt;/p&gt;

&lt;p&gt;if you'd rather not wire this up yourself, i put a free copy-paste hook (hooks.json plus the script) here: &lt;a href="https://site-export-rescue.pages.dev/cursor-prisma-drizzle-guard/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=stop-cursor-from-running-prisma-migrate-reset-on-the-wrong-database-3pch" rel="noopener noreferrer"&gt;site-export-rescue.pages.dev/cursor-prisma-drizzle-guard&lt;/a&gt;. it steers the agent's shell commands, not commands you run yourself. it blocks &lt;code&gt;prisma migrate reset&lt;/code&gt; and &lt;code&gt;drizzle-kit push --force&lt;/code&gt; unless it can see that the database url is local (localhost, 127.0.0.1, file or sqlite), and the page says what it doesn't catch, like &lt;code&gt;npm run&lt;/code&gt; scripts that hide a reset.&lt;/p&gt;




&lt;p&gt;Written with AI assistance and checked against the Prisma and Cursor docs.&lt;/p&gt;

</description>
      <category>cursor</category>
      <category>prisma</category>
      <category>drizzle</category>
      <category>database</category>
    </item>
  </channel>
</rss>
