Files
coder/docs/admin/templates/startup-coordination/example.md
T
Nick Vigilante c84aa564ba docs: normalize code-fence languages for Shiki compatibility (#27161)
Normalizes non-standard code-fence language tags across `docs/**` so a
strict highlighter (Shiki, used by Fumadocs) won't fail the build on an
unrecognized language, and unifies redundant synonym tags onto one
canonical form per language. The current renderer (Speed-Highlight)
detects the language from the code content, not the fence label, so this
drift wasn't visible until now.

## Changes

- `hcl` -> `tf` (199 fences, including indented ones nested in
numbered/bulleted lists). Shiki ships `hcl` and `terraform` as two
distinct grammars (not aliases); every `hcl`-tagged fence in `docs/**`
is actually Terraform resource/data/provider syntax, so the more
specific `terraform` grammar is correct for all of them. `tf` is Shiki's
own alias for that grammar, and it's also what GitHub's own markdown
renderer resolves to the same HCL/Terraform highlighting.
- `pwsh`/`powershell` -> `ps1`. Both `ps` and `ps1` are registered
PowerShell aliases in Shiki, but on GitHub's renderer only `.ps1` is a
registered file extension (`.ps` isn't), so `ps1` renders identically to
`powershell` there today while bare `ps` would silently lose
highlighting.
- `env` -> `dotenv` (a dedicated Shiki grammar for `KEY=VALUE` files)
- `text`/`output`/`none`/`url` -> `txt`. Same built-in plain-text
fallback either way, just shorter.
- `Dockerfile` -> `dockerfile` (lowercase)
- `bash`/`shell` -> `sh` (732 fences). Shiki and GitHub both alias all
three to a single shell grammar; this was already the style guide's
stated preference, just not enforced across the existing corpus until
now.
- `markdown` -> `md` (4 fences). Alias of the same grammar in both Shiki
and GitHub.
- `jsonc` -> `json` (1 fence). The block has no comments or trailing
commas, so it doesn't need the comments-capable grammar.
- `ts` -> `tsx` (2 fences, `docs/about/contributing/frontend.md`).
Verified the actual content tokenizes identically under both grammars,
and a sibling block in the same file already needs `tsx` for real JSX,
so unifying to one tag is safe for this file. Documented a caveat: `tsx`
mis-tokenizes the legacy angle-bracket type-assertion syntax
(`<Type>value`), which is invalid in real `.tsx` files anyway, so use
`value as Type` instead.
- `yml` -> `yaml` (1 fence)
- Updated `docs/.style/style-guide/formatting.md` to document all
canonical tags

`promql` (2 fences) and `caddyfile` (2 fences) are left as-is. Shiki
doesn't bundle a grammar for either, so they need a custom grammar
registration when the site adopts Shiki, rather than degrading to `txt`.
Tracked as follow-up work under DOCS-118 and
[DOCS-544](https://linear.app/codercom/issue/DOCS-544/vendor-a-local-promql-grammar-for-shiki-syntax-highlighting)
(promql).

Does not touch `offlinedocs/`.

Linear:
[DOCS-476](https://linear.app/codercom/issue/DOCS-476/normalize-docs-code-fence-languages-de-risk-shikifumadocs)

<details>
<summary>How the fence tags were verified</summary>

Each tag was tested against a real `shiki@latest` highlighter instance
(`codeToHtml`/`codeToTokens`) and cross-checked against GitHub's
`@wooorm/starry-night` grammar sources (the renderer that actually
displays these `.md` files today, in repo browsing and PR diffs), since
that's what determines whether brevity is safe before Shiki adoption:

```text
FAIL  env        -- Language `env` is not included in this bundle.
FAIL  Dockerfile -- Language `Dockerfile` is not included in this bundle.
FAIL  promql     -- Language `promql` is not included in this bundle.
FAIL  caddyfile  -- Language `caddyfile` is not included in this bundle.
FAIL  pwsh       -- Language `pwsh` is not included in this bundle.
FAIL  output     -- Language `output` is not included in this bundle.
```

`hcl` doesn't error in Shiki, since it's a real grammar, but that's
exactly the trap: it was silently rendering every fence with the generic
HCL grammar instead of the Terraform-specific one. Every `hcl`-tagged
fence in `docs/**` was manually checked against `origin/main` and is
genuinely Terraform content.

For `ts`/`tsx`, tokenizing the actual doc content confirmed identical
output under both grammars; a synthetic test with the legacy
angle-bracket cast syntax confirmed `tsx` degrades on that specific
construct, which the style guide now calls out.

The first normalization pass only matched fence tags at column 0
(`^```tag$`), missing tags indented inside numbered/bulleted lists. A
follow-up pass caught the remaining occurrences at any indentation
level.

</details>


---

*This PR description and the underlying changes were prepared with Coder
Agents assistance.*
2026-07-15 14:07:09 -04:00

7.2 KiB

Workspace Startup Coordination Examples

Script Example

This example shows a complete, production-ready script that starts Claude Code only after a repository has been cloned. It includes error handling, graceful degradation, and cleanup on exit:

#!/bin/bash
set -euo pipefail

UNIT_NAME="claude-code"
DEPENDENCIES="git-clone"
REPO_DIR="/workspace/repo"

# Track if sync started successfully
SYNC_STARTED=0

# Declare dependencies
if [ -n "$DEPENDENCIES" ]; then
  if command -v coder > /dev/null 2>&1; then
    IFS=',' read -ra DEPS <<< "$DEPENDENCIES"
    for dep in "${DEPS[@]}"; do
      dep=$(echo "$dep" | xargs)
      if [ -n "$dep" ]; then
        echo "Waiting for dependency: $dep"
        coder exp sync want "$UNIT_NAME" "$dep" > /dev/null 2>&1 || \
          echo "Warning: Failed to register dependency $dep, continuing..."
      fi
    done
  else
    echo "Coder CLI not found, running without sync coordination"
  fi
fi

# Start sync and track success
if [ -n "$UNIT_NAME" ]; then
  if command -v coder > /dev/null 2>&1; then
    if coder exp sync start "$UNIT_NAME" > /dev/null 2>&1; then
      SYNC_STARTED=1
      echo "Started sync: $UNIT_NAME"
    else
      echo "Sync start failed or not available, continuing without sync..."
    fi
  fi
fi

# Ensure completion on exit (even if script fails)
cleanup_sync() {
  if [ "$SYNC_STARTED" -eq 1 ] && [ -n "$UNIT_NAME" ]; then
    echo "Completing sync: $UNIT_NAME"
    coder exp sync complete "$UNIT_NAME" > /dev/null 2>&1 || \
      echo "Warning: Sync complete failed, but continuing..."
  fi
}
trap cleanup_sync EXIT

# Now do the actual work
echo "Repository cloned, starting Claude Code"
cd "$REPO_DIR"
claude

This script demonstrates several best practices:

  • Checking for Coder CLI availability before using sync commands
  • Tracking whether coder exp sync started successfully
  • Using trap to ensure completion even if the script exits early
  • Graceful degradation when coder exp sync isn't available
  • Redirecting coder exp sync output to reduce noise in logs

Template Migration Example

Below is a simple example Docker template that clones Miguel Grinberg's example Flask repo using the git-clone module and installs the required dependencies for the project:

  • Python development headers (required for building some Python packages)
  • Python dependencies from the project's requirements.txt

We've omitted some details (such as persistent storage) for brevity, but these are easily added.

Before

data "coder_provisioner" "me" {}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}

resource "docker_container" "workspace" {
  count = data.coder_workspace.me.start_count
  image = "codercom/enterprise-base:ubuntu"
  name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
  entrypoint = ["sh", "-c", coder_agent.main.init_script]
  env        = [
    "CODER_AGENT_TOKEN=${coder_agent.main.token}",
    ]
}

resource "coder_agent" "main" {
  arch           = data.coder_provisioner.me.arch
  os             = "linux"
}

module "git-clone" {
  count             = data.coder_workspace.me.start_count
  source            = "registry.coder.com/coder/git-clone/coder"
  version           = "1.2.3"
  agent_id          = coder_agent.main.id
  url               = "https://github.com/miguelgrinberg/microblog"
}

resource "coder_script" "setup" {
  count              = data.coder_workspace.me.start_count
  agent_id           = coder_agent.main.id
  display_name       = "Installing Dependencies"
  run_on_start       = true
  script             = <<EOT
    sudo apt-get update
    sudo apt-get install --yes python-dev-is-python3
    cd ${module.git-clone[count.index].repo_dir}
    python3 -m venv .venv
    source .venv/bin/activate
    pip install -r requirements.txt
  EOT
}

We can note the following issues in the above template:

  1. There is a race between cloning the repository and the pip install commands, which can lead to failed workspace startups in some cases.
  2. The apt commands can run independently of the git clone command, meaning that there is a potential speedup here.

Based on the above, we can improve both the startup time and reliability of the template by splitting the monolithic startup script into multiple independent scripts:

  • Install apt dependencies
  • Install pip dependencies (depends on the git-clone module and the above step)

After

Here is the updated version of the template:

data "coder_provisioner" "me" {}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}

resource "docker_container" "workspace" {
  count = data.coder_workspace.me.start_count
  image = "codercom/enterprise-base:ubuntu"
  name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
  entrypoint = ["sh", "-c", coder_agent.main.init_script]
  env        = [
    "CODER_AGENT_TOKEN=${coder_agent.main.token}",
    ]
}

resource "coder_agent" "main" {
  arch           = data.coder_provisioner.me.arch
  os             = "linux"
}

module "git-clone" {
  count             = data.coder_workspace.me.start_count
  source            = "registry.coder.com/coder/git-clone/coder"
  version           = "1.2.3"
  agent_id          = coder_agent.main.id
  url               = "https://github.com/miguelgrinberg/microblog/"
  post_clone_script = <<-EOT
    coder exp sync start git-clone && coder exp sync complete git-clone
  EOT
}

resource "coder_script" "apt-install" {
  count              = data.coder_workspace.me.start_count
  agent_id           = coder_agent.main.id
  display_name       = "Installing APT Dependencies"
  run_on_start       = true
  script             = <<EOT
    trap 'coder exp sync complete apt-install' EXIT
    coder exp sync start apt-install

    sudo apt-get update
    sudo apt-get install --yes python-dev-is-python3
  EOT
}

resource "coder_script" "pip-install" {
  count              = data.coder_workspace.me.start_count
  agent_id           = coder_agent.main.id
  display_name       = "Installing Python Dependencies"
  run_on_start       = true
  script             = <<EOT
    trap 'coder exp sync complete pip-install' EXIT
    coder exp sync want pip-install git-clone apt-install
    coder exp sync start pip-install

    cd ${module.git-clone[count.index].repo_dir}
    python3 -m venv .venv
    source .venv/bin/activate
    pip install -r requirements.txt
  EOT
}

A short summary of the changes:

  • We've broken the monolithic "setup" script into two separate scripts: one for the apt commands, and one for the pip commands.
    • In each script, we've added a coder exp sync start $SCRIPT_NAME command to mark the startup script as started.
    • We've also added an exit trap to ensure that we mark the startup scripts as completed. Without this, the coder exp sync wait command would eventually time out.
  • We have used the post_clone_script feature of the git-clone module to allow waiting on the Git repository clone.
  • In the pip-install script, we have declared a dependency on both git-clone and apt-install.

With these changes, the startup time has been reduced significantly and there is no longer any possibility of a race condition.