Skip to content

fix(infra): create the demo Workers through the scripts API - #19

Merged
reecelikesramen merged 2 commits into
mainfrom
claude/project-thread-3ala5u
Sep 30, 2026
Merged

reecelikesramen merged 2 commits into
mainfrom
claude/project-thread-3ala5u

Conversation

@reecelikesramen

@reecelikesramen reecelikesramen commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Before: Infra apply can't finish the demo Workers. With Workers Scripts Write on the token, cloudflare_worker created the three Workers. Then every route failed with "Cannot configure a route for a Worker which does not exist": a Worker with no code doesn't count as a script for routes.

After: each demo Worker is a cloudflare_workers_script, the same resource the router uses. It starts as a placeholder that answers 503 "This demo hasn't been deployed yet.", so the routes apply before the first deploy. pywire/pywire's Deploy Examples workflow then uploads the real code, and ignore_changes = all keeps Terraform from undoing it. Terraform owns each Worker's existence and routes, and the workflow owns its code and settings.

A removed block with destroy = false drops the Workers created by the first apply from state without deleting them. The scripts then upload to the same names. The routes now reference the scripts. workers.dev and preview URLs stay off, because each example's wrangler.toml sets them on deploy.

🤖 Generated with Claude Code

cloudflare_worker's create call returns 403 for this token even with
Workers Scripts Write and Workers Editor. The router already works through
cloudflare_workers_script, so create each demo Worker the same way with a
placeholder, and ignore later changes because the Deploy Examples workflow
owns the code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUEPGc2SgMUHxx4Z28HS8v
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Terraform plan — 📝 changes

cloudflare_email_routing_address.destinations["[email]"]: Refreshing state... [id=ba2224a5004345f19ad66c5a71343976]
cloudflare_dns_record.docs_cname: Refreshing state... [id=e562a5382eacb08985eaba0d4907fb2c]
cloudflare_email_routing_address.destinations["[email]"]: Refreshing state... [id=b465118aef02490e9f584146ce54f65b]
cloudflare_r2_bucket.cdn: Refreshing state... [id=pywire-cdn]
cloudflare_email_routing_settings.main: Refreshing state... [id=9f1c5e8c997d29163e81a24f7b26dc35]
cloudflare_pages_project.landing: Refreshing state... [id=pywire-landing]
cloudflare_pages_project.docs: Refreshing state... [id=pywire-docs]
cloudflare_ruleset.allow_llm_crawlers: Refreshing state... [id=375241922de4405a8b77b812e6e25730]
cloudflare_dns_record.vscode_verification: Refreshing state... [id=8ac56f7d47b8d2401df848706ad88cdd]
cloudflare_dns_record.demo: Refreshing state... [id=b0bddc628bb491021da19902143848d8]
cloudflare_email_routing_rule.maintainers_group: Refreshing state... [id=2f20558a55f04e10959ead934f397730]
cloudflare_email_routing_rule.individual_aliases["hello"]: Refreshing state... [id=1ff2a9d2add740089d4c7952455e4b56]
cloudflare_email_routing_rule.individual_aliases["reece"]: Refreshing state... [id=d14078e65aea4106adf662dc46d54710]
cloudflare_workers_script.router: Refreshing state... [id=pywire-router]
cloudflare_pages_domain.docs: Refreshing state... [id=docs.pywire.dev]
cloudflare_pages_domain.landing: Refreshing state... [id=pywire.dev]
cloudflare_dns_record.nightly: Refreshing state... [id=6a2d6b6878cc8452e1e29dd1f30c6dd1]
cloudflare_workers_route.catch_all: Refreshing state... [id=a70946d772474786ada02b87b9fd05cf]
cloudflare_workers_route.nightly: Refreshing state... [id=874d0be239a04de79226d64cd941e9bf]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

 # cloudflare_worker.demo["edge-stateless"] will no longer be managed by Terraform, but will not be destroyed
 # (destroy = false is set in the configuration)
 . resource "cloudflare_worker" "demo" {
        id             = "84506ce1127f41b89449e73c65944e56"
        name           = "pywire-demo-edge-stateless"
        tags           = []
        # (8 unchanged attributes hidden)
    }

 # cloudflare_worker.demo["form-builder"] will no longer be managed by Terraform, but will not be destroyed
 # (destroy = false is set in the configuration)
 . resource "cloudflare_worker" "demo" {
        id             = "bee6fc294eb94d35ba9e6121e76abe51"
        name           = "pywire-demo-form-builder"
        tags           = []
        # (8 unchanged attributes hidden)
    }

 # cloudflare_worker.demo["site"] will no longer be managed by Terraform, but will not be destroyed
 # (destroy = false is set in the configuration)
 . resource "cloudflare_worker" "demo" {
        id             = "bc954fb642f5457a900207a9b69eac6f"
        name           = "pywire-demo"
        tags           = []
        # (8 unchanged attributes hidden)
    }

  # cloudflare_workers_route.demo["edge-stateless"] will be created
  + resource "cloudflare_workers_route" "demo" {
      + id      = (known after apply)
      + pattern = "demo.pywire.dev/edge-stateless"
      + script  = "pywire-demo-edge-stateless"
      + zone_id = "9f1c5e8c997d29163e81a24f7b26dc35"
    }

  # cloudflare_workers_route.demo["edge-stateless/*"] will be created
  + resource "cloudflare_workers_route" "demo" {
      + id      = (known after apply)
      + pattern = "demo.pywire.dev/edge-stateless/*"
      + script  = "pywire-demo-edge-stateless"
      + zone_id = "9f1c5e8c997d29163e81a24f7b26dc35"
    }

  # cloudflare_workers_route.demo["form-builder"] will be created
  + resource "cloudflare_workers_route" "demo" {
      + id      = (known after apply)
      + pattern = "demo.pywire.dev/form-builder"
      + script  = "pywire-demo-form-builder"
      + zone_id = "9f1c5e8c997d29163e81a24f7b26dc35"
    }

  # cloudflare_workers_route.demo["form-builder/*"] will be created
  + resource "cloudflare_workers_route" "demo" {
      + id      = (known after apply)
      + pattern = "demo.pywire.dev/form-builder/*"
      + script  = "pywire-demo-form-builder"
      + zone_id = "9f1c5e8c997d29163e81a24f7b26dc35"
    }

  # cloudflare_workers_route.demo["site"] will be created
  + resource "cloudflare_workers_route" "demo" {
      + id      = (known after apply)
      + pattern = "demo.pywire.dev/*"
      + script  = "pywire-demo"
      + zone_id = "9f1c5e8c997d29163e81a24f7b26dc35"
    }

  # cloudflare_workers_script.demo["edge-stateless"] will be created
  + resource "cloudflare_workers_script" "demo" {
      + account_id          = "abd8226d8d910afcfa1d370097e6336a"
      + bindings            = (known after apply)
      + compatibility_date  = "2026-09-01"
      + compatibility_flags = (known after apply)
      + content             = <<-EOT
            export default {
              fetch() {
                return new Response("This demo hasn't been deployed yet.", { status: 503 });
              },
            };
        EOT
      + created_on          = (known after apply)
      + etag                = (known after apply)
      + handlers            = (known after apply)
      + has_assets          = (known after apply)
      + has_modules         = (known after apply)
      + id                  = (known after apply)
      + last_deployed_from  = (known after apply)
      + logpush             = false
      + main_module         = "placeholder.js"
      + migration_tag       = (known after apply)
      + modified_on         = (known after apply)
      + named_handlers      = (known after apply)
      + placement           = (known after apply)
      + script_name         = "pywire-demo-edge-stateless"
      + startup_time_ms     = (known after apply)
      + tail_consumers      = (known after apply)
      + usage_model         = "standard"
    }

  # cloudflare_workers_script.demo["form-builder"] will be created
  + resource "cloudflare_workers_script" "demo" {
      + account_id          = "abd8226d8d910afcfa1d370097e6336a"
      + bindings            = (known after apply)
      + compatibility_date  = "2026-09-01"
      + compatibility_flags = (known after apply)
      + content             = <<-EOT
            export default {
              fetch() {
                return new Response("This demo hasn't been deployed yet.", { status: 503 });
              },
            };
        EOT
      + created_on          = (known after apply)
      + etag                = (known after apply)
      + handlers            = (known after apply)
      + has_assets          = (known after apply)
      + has_modules         = (known after apply)
      + id                  = (known after apply)
      + last_deployed_from  = (known after apply)
      + logpush             = false
      + main_module         = "placeholder.js"
      + migration_tag       = (known after apply)
      + modified_on         = (known after apply)
      + named_handlers      = (known after apply)
      + placement           = (known after apply)
      + script_name         = "pywire-demo-form-builder"
      + startup_time_ms     = (known after apply)
      + tail_consumers      = (known after apply)
      + usage_model         = "standard"
    }

  # cloudflare_workers_script.demo["site"] will be created
  + resource "cloudflare_workers_script" "demo" {
      + account_id          = "abd8226d8d910afcfa1d370097e6336a"
      + bindings            = (known after apply)
      + compatibility_date  = "2026-09-01"
      + compatibility_flags = (known after apply)
      + content             = <<-EOT
            export default {
              fetch() {
                return new Response("This demo hasn't been deployed yet.", { status: 503 });
              },
            };
        EOT
      + created_on          = (known after apply)
      + etag                = (known after apply)
      + handlers            = (known after apply)
      + has_assets          = (known after apply)
      + has_modules         = (known after apply)
      + id                  = (known after apply)
      + last_deployed_from  = (known after apply)
      + logpush             = false
      + main_module         = "placeholder.js"
      + migration_tag       = (known after apply)
      + modified_on         = (known after apply)
      + named_handlers      = (known after apply)
      + placement           = (known after apply)
      + script_name         = "pywire-demo"
      + startup_time_ms     = (known after apply)
      + tail_consumers      = (known after apply)
      + usage_model         = "standard"
    }

Plan: 8 to add, 0 to change, 0 to destroy.

Warning: Some objects will no longer be managed by Terraform

If you apply this plan, Terraform will discard its tracking information for
the following objects, but it will not delete them:
 - cloudflare_worker.demo["site"]
 - cloudflare_worker.demo["edge-stateless"]
 - cloudflare_worker.demo["form-builder"]

After applying this plan, Terraform will no longer manage these objects. You
will need to import them into Terraform to manage them again.

Routes can't point at a code-less cloudflare_worker, which is why the
second apply failed after creating them. Forget those resources with a
removed block instead of destroying them, and let the placeholder
scripts upload to the same names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUEPGc2SgMUHxx4Z28HS8v
@reecelikesramen
reecelikesramen merged commit de636bd into main Sep 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants