From ffa83a4ebce5f1bcdd8072f53d3f02b1b5f9c25c Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Wed, 14 Jan 2026 14:40:38 +0200 Subject: [PATCH] docs: add documentation for coder script ordering (#21090) This Pull request adds documentation and guidance for the Coder script ordering feature. We: * explain the use case, benefits, and requirements. * provide example configuration snippets * discuss best practices and troubleshooting --------- Co-authored-by: Cian Johnston Co-authored-by: DevCats --- .../templates/startup-coordination/example.md | 215 +++++++++++++ .../templates/startup-coordination/index.md | 50 ++++ .../startup-coordination/troubleshooting.md | 98 ++++++ .../templates/startup-coordination/usage.md | 283 ++++++++++++++++++ docs/manifest.json | 23 ++ docs/user-guides/workspace-lifecycle.md | 4 +- 6 files changed, 672 insertions(+), 1 deletion(-) create mode 100644 docs/admin/templates/startup-coordination/example.md create mode 100644 docs/admin/templates/startup-coordination/index.md create mode 100644 docs/admin/templates/startup-coordination/troubleshooting.md create mode 100644 docs/admin/templates/startup-coordination/usage.md diff --git a/docs/admin/templates/startup-coordination/example.md b/docs/admin/templates/startup-coordination/example.md new file mode 100644 index 0000000000..290394cf47 --- /dev/null +++ b/docs/admin/templates/startup-coordination/example.md @@ -0,0 +1,215 @@ +# 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: + +```bash +#!/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](./usage.md#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](https://github.com/miguelgrinberg/microblog/) using the [`git-clone` module](https://registry.coder.com/modules/coder/git-clone) 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 + +```terraform +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 = < [!NOTE] +> This feature is experimental and may change without notice in future releases. + +When workspaces start, scripts often need to run in a specific order. +For example, an IDE or coding agent might need the repository cloned +before it can start. Without explicit coordination, these scripts can +race against each other, leading to startup failures and inconsistent +workspace states. + +Coder's workspace startup coordination feature lets you declare +dependencies between startup scripts and ensure they run in the correct order. +This eliminates race conditions and makes workspace startup predictable and +reliable. + +## Why use this? + +Simply placing all of your workspace initialization logic in a single script works, but leads to slow workspace startup times. +Breaking this out into multiple independent `coder_script` resources improves startup times by allowing the scripts to run in parallel. +However, this can lead to intermittent failures between dependent scripts due to timing issues. +Up until now, template authors have had to rely on manual coordination methods (for example, touching a file upon completion). +The goal of startup script coordination is to provide a single reliable source of truth for coordination between workspace startup scripts. + +## Quick Start + +To start using workspace startup coordination, follow these steps: + +1. Set the environment variable `CODER_AGENT_SOCKET_SERVER_ENABLED=true` in your template to enable the agent socket server. The environment variable *must* be readable to the agent process. For example, in a template using the `kreuzwerker/docker` provider: + + ```terraform + resource "docker_container" "workspace" { + image = "codercom/enterprise-base:ubuntu" + env = [ + "CODER_AGENT_TOKEN=${coder_agent.main.token}", + "CODER_AGENT_SOCKET_SERVER_ENABLED=true", + ] + } + ``` + +1. Add calls to `coder exp sync (start|complete)` in your startup scripts where required: + + ```bash + trap 'coder exp sync complete my-script' EXIT + coder exp sync want my-script my-other-script + coder exp sync start my-script + # Existing startup logic + ``` + +For more information, refer to the [usage documentation](./usage.md), [troubleshooting documentation](./troubleshooting.md), or view our [examples](./example.md). diff --git a/docs/admin/templates/startup-coordination/troubleshooting.md b/docs/admin/templates/startup-coordination/troubleshooting.md new file mode 100644 index 0000000000..001fb50ec0 --- /dev/null +++ b/docs/admin/templates/startup-coordination/troubleshooting.md @@ -0,0 +1,98 @@ +# Workspace Startup Coordination Troubleshooting + +> [!NOTE] +> This feature is experimental and may change without notice in future releases. + +## Test Sync Availability + +From a workspace terminal, test if sync is working using `coder exp sync ping`: + +```bash +coder exp sync ping +``` + +* If sync is working, expect the output to be `Success`. +* Otherwise, you will see an error message similar to the below: + +```bash +error: connect to agent socket: connect to socket: dial unix /tmp/coder-agent.sock: connect: permission denied +``` + +## Check Unit Status + +You can check the status of a specific unit using `coder exp sync status`: + +```bash +coder exp sync status git-clone +``` + +If the unit exists, you will see output similar to the below: + +```bash +# coder exp sync status git-clone +Unit: git-clone +Status: completed +Ready: true +``` + +If the unit is not known to the agent, you will see output similar to the below: + +```bash +# coder exp sync status doesnotexist +Unit: doesnotexist +Status: not registered +Ready: true + +Dependencies: +No dependencies found +``` + +## Common Issues + +### Socket not enabled + +If the Coder Agent Socket Server is not enabled, you will see an error message similar to the below when running `coder exp sync ping`: + +```bash +error: connect to agent socket: connect to socket: dial unix /tmp/coder-agent.sock: connect: no such file or directory +``` + +Verify `CODER_AGENT_SOCKET_SERVER_ENABLED=true` is set in the Coder agent's environment: + +```bash +tr '\0' '\n' < /proc/$(pidof -s coder)/environ | grep CODER_AGENT_SOCKET_SERVER_ENABLED +``` + +If the output of the above command is empty, review your template and ensure that the environment variable is set such that it is readable by the Coder agent process. Setting it on the `coder_agent` resource directly is **not** sufficient. + +## Workspace startup script hangs + +If the workspace startup scripts appear to 'hang', one or more of your startup scripts may be waiting for a dependency that never completes. + +* Inside the workspace, review `/tmp/coder-script-*.log` for more details on your script's execution. + > **Tip:** add `set -x` to the top of your script to enable debug mode and update/restart the workspace. +* Review your template and verify that `coder exp sync complete ` is called after the script completes e.g. with an exit trap. +* View the unit status using `coder exp sync status `. + +## Workspace startup scripts fail + +If the workspace startup scripts fail: + +* Review `/tmp/coder-script-*.log` inside the workspace for script errors. +* Verify the Coder CLI is available in `$PATH` inside the workspace: + + ```bash + command -v coder + ``` + +## Cycle detected + +If you see an error similar to the below in your startup script logs, you have defined a cyclic dependency: + +```bash +error: declare dependency failed: cannot add dependency: adding edge for unit "bar": failed to add dependency +adding edge (bar -> foo): cycle detected +``` + +To fix this, review your dependency declarations and redesign them to remove the cycle. It may help to draw out the dependency graph to find +the cycle. diff --git a/docs/admin/templates/startup-coordination/usage.md b/docs/admin/templates/startup-coordination/usage.md new file mode 100644 index 0000000000..89b0ccc113 --- /dev/null +++ b/docs/admin/templates/startup-coordination/usage.md @@ -0,0 +1,283 @@ +# Workspace Startup Coordination Usage + +> [!NOTE] +> This feature is experimental and may change without notice in future releases. + +Startup coordination is built around the concept of **units**. You declare units in your Coder workspace template using the `coder exp sync` command in `coder_script` resources. When the Coder agent starts, it keeps an in-memory directed acyclic graph (DAG) of all units of which it is aware. When you need to synchronize with another unit, you can use `coder exp sync start $UNIT_NAME` to block until all dependencies of that unit have been marked complete. + +## What is a unit? + +A **unit** is a named phase of work, typically corresponding to a script or initialization +task. + +- Units **may** declare dependencies on other units, creating an explicit ordering for workspace initialization. +- Units **must** be registered before they can be marked as complete. +- Units **may** be marked as dependencies before they are registered. +- Units **must not** declare cyclic dependencies. Attempting to create a cyclic dependency will result in an error. + +## Requirements + +> [!IMPORTANT] +> The `coder exp sync` command is only available from Coder version >=v2.30 onwards. + +To use startup dependencies in your templates, you must: + +- Enable the Coder Agent Socket Server. +- Modify your workspace startup scripts to run in parallel and declare dependencies as required using `coder exp sync`. + +### Enable the Coder Agent Socket Server + +The agent socket server provides the communication layer for startup +coordination. To enable it, set `CODER_AGENT_SOCKET_SERVER_ENABLED=true` in the environment in which the agent is running. +The exact method for doing this depends on your infrastructure platform: + +
+ +#### Docker / Podman + +```hcl +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)}" + + env = [ + "CODER_AGENT_SOCKET_SERVER_ENABLED=true" + ] + + command = ["sh", "-c", coder_agent.main.init_script] +} +``` + +#### Kubernetes + +```hcl +resource "kubernetes_pod" "main" { + count = data.coder_workspace.me.start_count + + metadata { + name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + namespace = var.workspaces_namespace + } + + spec { + container { + name = "dev" + image = "codercom/enterprise-base:ubuntu" + command = ["sh", "-c", coder_agent.main.init_script] + + env { + name = "CODER_AGENT_SOCKET_SERVER_ENABLED" + value = "true" + } + } + } +} +``` + +#### AWS EC2 / VMs + +For virtual machines, pass the environment variable through cloud-init or your +provisioning system: + +```hcl +locals { + agent_env = { + "CODER_AGENT_SOCKET_SERVER_ENABLED" = "true" + } +} + +# In your cloud-init userdata template: +# %{ for key, value in local.agent_env ~} +# export ${key}="${value}" +# %{ endfor ~} +``` + +
+ +### Declare Dependencies in your Workspace Startup Scripts + +
+ +#### Single Dependency + +Here's a simple example of a script that depends on another unit completing +first: + +```bash +#!/bin/bash +UNIT_NAME="my-setup" + +# Declare dependency on git-clone +coder exp sync want "$UNIT_NAME" "git-clone" + +# Wait for dependencies and mark as started +coder exp sync start "$UNIT_NAME" + +# Do your work here +echo "Running after git-clone completes" + +# Signal completion +coder exp sync complete "$UNIT_NAME" +``` + +This script will wait until the `git-clone` unit completes before starting its +own work. + +#### Multiple Dependencies + +If your unit depends on multiple other units, you can declare all dependencies +before starting: + +```bash +#!/bin/bash +UNIT_NAME="my-app" +DEPENDENCIES="git-clone,env-setup,database-migration" + +# Declare all dependencies +if [ -n "$DEPENDENCIES" ]; then + IFS=',' read -ra DEPS <<< "$DEPENDENCIES" + for dep in "${DEPS[@]}"; do + dep=$(echo "$dep" | xargs) # Trim whitespace + if [ -n "$dep" ]; then + coder exp sync want "$UNIT_NAME" "$dep" + fi + done +fi + +# Wait for all dependencies +coder exp sync start "$UNIT_NAME" + +# Your work here +echo "All dependencies satisfied, starting application" + +# Signal completion +coder exp sync complete "$UNIT_NAME" +``` + +
+ +## Best Practices + +### Test your changes before rolling out to all users + +Before rolling out to all users: + +1. Create a test workspace from the updated template +2. Check workspace build logs for sync messages +3. Verify all units reach "completed" status +4. Test workspace functionality + +Once you're satisfied, [promote the new template version](../../../reference/cli/templates_versions_promote.md). + +### Handle missing CLI gracefully + +Not all workspaces will have the Coder CLI available in `$PATH`. Check for availability of the Coder CLI before using +sync commands: + +```bash +if command -v coder > /dev/null 2>&1; then + coder exp sync start "$UNIT_NAME" +else + echo "Coder CLI not available, continuing without coordination" +fi +``` + +### Complete units that start successfully + +Units **must** call `coder exp sync complete` to unblock dependent units. Use `trap` to ensure +completion even if your script exits early or encounters errors: + +```bash + +SYNC_STARTED=0 +if coder exp sync start "$UNIT_NAME"; then + SYNC_STARTED=1 +fi + +cleanup_sync() { + if [ "$SYNC_STARTED" -eq 1 ]; then + coder exp sync complete "$UNIT_NAME" + fi +} +trap cleanup_sync EXIT +``` + +### Use descriptive unit names + +Names should explain what the unit does, not its position in a sequence: + +- Good: `git-clone`, `env-setup`, `database-migration` +- Avoid: `step1`, `init`, `script-1` + +### Prefix a unique name to your units + +When using `coder exp sync` in modules, note that unit names like `git-clone` might be common. Prefix the name of your module to your units to +ensure that your unit does not conflict with others. + +- Good: `.git-clone`, `.claude` +- Bad: `git-clone`, `claude` + +### Document dependencies + +Add comments explaining why dependencies exist: + +```hcl +resource "coder_script" "ide_setup" { + # Depends on git-clone because we need .vscode/extensions.json + # Depends on env-setup because we need $NODE_PATH configured + script = <<-EOT + coder exp sync want "ide-setup" "git-clone" + coder exp sync want "ide-setup" "env-setup" + # ... + EOT +} +``` + +### Avoid circular dependencies + +The Coder Agent detects and rejects circular dependencies, but they indicate a design problem: + +```bash +# This will fail +coder exp sync want "unit-a" "unit-b" +coder exp sync want "unit-b" "unit-a" +``` + +## Frequently Asked Questions + +### How do I identify scripts that can benefit from startup coordination? + +Look for these patterns in existing templates: + +- `sleep` commands used to order scripts +- Using files to coordinate startup between scripts (e.g. `touch /tmp/startup-complete`) +- Scripts that fail intermittently on startup +- Comments like "must run after X" or "wait for Y" + +### Will this slow down my workspace? + +No. The socket server adds minimal overhead, and the default polling interval is 1 +second, so waiting for dependencies adds at most a few seconds to startup. +You are more likely to notice an improvement in startup times as it becomes easier to manage complex dependencies in parallel. + +### How do units interact with each other? + +Units with no dependencies run immediately and in parallel. +Only units with unsatisfied dependencies wait for their dependencies. + +### How long can a dependency take to complete? + +By default, `coder exp sync start` has a 5-minute timeout to prevent indefinite hangs. +Upon timeout, the command will exit with an error code and print `timeout waiting for dependencies of unit ` to stderr. + +You can adjust this timeout as necessary for long-running operations: + +```bash +coder exp sync start "long-operation" --timeout 10m +``` + +### Is state stored between restarts? + +No. Sync state is kept in-memory only and resets on workspace restart. +This is intentional to ensure clean initialization on every start. diff --git a/docs/manifest.json b/docs/manifest.json index 10385ecb30..06a99a52ea 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -667,6 +667,29 @@ "description": "Log workspace processes", "path": "./admin/templates/extending-templates/process-logging.md", "state": ["premium"] + }, + { + "title": "Startup Dependencies", + "description": "Coordinate workspace startup with dependency management", + "path": "./admin/templates/startup-coordination/index.md", + "state": ["early access"], + "children": [ + { + "title": "Usage", + "description": "How to use startup coordination", + "path": "./admin/templates/startup-coordination/usage.md" + }, + { + "title": "Troubleshooting", + "description": "Troubleshoot startup coordination", + "path": "./admin/templates/startup-coordination/troubleshooting.md" + }, + { + "title": "Examples", + "description": "Examples of startup coordination", + "path": "./admin/templates/startup-coordination/example.md" + } + ] } ] }, diff --git a/docs/user-guides/workspace-lifecycle.md b/docs/user-guides/workspace-lifecycle.md index f09cd63b80..bad631b6dc 100644 --- a/docs/user-guides/workspace-lifecycle.md +++ b/docs/user-guides/workspace-lifecycle.md @@ -60,7 +60,9 @@ as [JetBrains](./workspace-access/jetbrains/index.md) or Once started, the Coder agent is responsible for running your workspace startup scripts. These may configure tools, service connections, or personalization with -[dotfiles](./workspace-dotfiles.md). +[dotfiles](./workspace-dotfiles.md). For complex initialization with multiple +dependent scripts, see +[Workspace Startup Coordination](../admin/templates/startup-coordination/index.md). Once these steps have completed, your workspace will now be in the `Running` state. You can access it via any of the [supported methods](./index.md), stop it