Magic Comments
Section titled “Magic Comments”Magic comments configure script behavior from the top of the file itself. You do not change code, write a configuration file, or set environment variables; you edit a comment and the behavior changes.
// @mode worker// @cors reflective// @timeout 5000// @log-level standard// @ai true
// Your code starts here...return { hello: 'world' };How magic comments work
Section titled “How magic comments work”Magic comments are parsed at script load time. Put them at the very top of the file, before any code, imports, or expressions.
Rules:
- Start with
//followed by a space and@ - One comment per line
- Comment names are case-sensitive
- A comment’s value overrides the default
- The validation API parses and checks them
Validation example:
# Validate magic comments in a scripthoody exec validate magic-comments \ --code "// @mode worker\n// @cors reflective\n// @timeout 5000\nreturn {};"const containerClient = await client.withContainer({ id: CONTAINER_ID, project_id: PROJECT_ID, server: SERVER});const result = await containerClient.exec.validate.validateMagicComments({ code: '// @mode worker\n// @cors reflective\n// @timeout 5000\nreturn {};'});console.log(result.data); // { magicComments: { ... }, returnType: { ... }, message: "Magic comments parsed successfully" }curl -s -X POST "https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/v1/exec/validate/magic-comments" \ -H "Content-Type: application/json" \ -d '{ "code": "// @mode worker\n// @cors reflective\n// @timeout 5000\nreturn {};" }'One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Validates a script’s magic comments and returns the parsed result, without running the script.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/v1/exec/validate/magic-comments&method=POST&json={"code":"//%20@mode%20worker\n//%20@cors%20reflective\n//%20@timeout%205000\nreturn%20{};"}&response=transparent Execution and performance
Section titled “Execution and performance”| Comment | Values | Default | Description |
|---|---|---|---|
@mode | worker | serverless | serverless | Execution mode. Worker mode keeps a stateful persistent VM; serverless mode starts a fresh, isolated VM for each request |
@enabled | true | false | true | Enable or disable script execution. A disabled script does not run |
@timeout | ms | 90s/5m/2h | 0 | unlimited | 3600000 | Soft request timeout (default 1 hour). Bare number = ms; unit suffixes (s/m/h/d) accepted. Past the deadline an un-started response gets 504; the timeout does not interrupt an already-streaming response or kill in-flight VM work. 0/unlimited = no timeout |
@await-promises | true | false | true | Auto-await returned promises before sending response |
@concurrent | number | true | false | true | Max concurrent executions (applies in both worker and serverless mode, and to cron). true = unlimited, false = single serial execution, N = at most N in parallel |
@schedule | cron expression | (none) | Run the script on a cron schedule (e.g., 0 9 * * *). See Scheduling |
Examples:
// @mode worker // Persistent VM, shared state// @timeout 60000 // 60 second timeout// @concurrent false // Serverless: process one at a time// @timeout 0 // No timeout (use with care)// @timeout unlimited // Same as @timeout 0CORS configuration
Section titled “CORS configuration”| Comment | Values | Default | Description |
|---|---|---|---|
@cors | reflective | * | URL | none | reflective | Origin policy for the actual response. Omitting @cors keeps the server’s global reflective CORS; none strips CORS headers; reflective/*/URL set the allowed origin |
@cors-credentials | true | false | false | Allow credentials (cookies, auth headers). Never combined with @cors * |
@cors-methods | GET,POST,... | (server default) | Access-Control-Allow-Methods is preflight-only: the browser reads it from the OPTIONS response, which the server answers globally, so the per-script value is not applied and the server default is used |
@cors-headers | Authorization,... | (server default) | Access-Control-Allow-Headers is preflight-only (server-answered, not applied per-script); the per-script value drives Access-Control-Expose-Headers on the actual response |
@cors-max-age | number (seconds) | (server default) | Preflight cache hint. Not applied per-script: the server answers the OPTIONS preflight globally |
Examples:
// @cors reflective // Mirror request origin (development)// @cors * // Allow any origin (testing)// @cors https://app.example.com // Specific origin (production)// @cors none // No CORS headers// @cors-credentials true // Allow cookies// @cors-methods GET,POST // Only allow GET and POSTLogging and debugging
Section titled “Logging and debugging”| Comment | Values | Default | Description |
|---|---|---|---|
@log-level | none | minimal | standard | full | debug | standard | Logging verbosity |
@debug | true | false | false | Shortcut for @log-level debug |
@log-request-body | full | redacted | off | true | false | full | Log incoming request bodies |
@log-response-body | full | redacted | off | true | false | full | Log response bodies |
@log-max-body-size | bytes | 512kb/1mb | 1048576 | Max body size to log. Bare number = bytes; unit suffixes (kb/mb/gb) accepted |
@log-exclude-headers | header names | authorization cookie x-token | Headers to exclude from logs (e.g., authorization cookie) |
@log-retention-days | 0 to 3650 | 14 | Days to keep log files (values above the 3650-day cap are clamped) |
@debug-instrument | true | false | false | Enable code instrumentation for detailed tracing |
Examples:
// @log-level full // Maximum logging// @log-request-body true // Log request payloads// @log-response-body true // Log response payloads// @log-exclude-headers authorization cookie // Hide sensitive headers// @log-retention-days 30 // Keep logs for 30 days// @debug-instrument true // Detailed code tracingLog levels:
none: no loggingminimal: the request line plus the final status and duration, without the detailed request and response logsstandard: request metadata and headers (the default)full: everything instandardplus request and response bodies, with body capture still gated by@log-request-bodyand@log-response-bodydebug: everything infullplus internal execution details such as VM creation and module loading
AI integration
Section titled “AI integration”| Comment | Values | Default | Description |
|---|---|---|---|
@ai | true | false | true | Enable AI helpers. Injects model, openai, ai, generateText, streamText, and generateObject |
@ai-model | model name | hoody-ai/hoody-free | AI model to use. Overrides the server default (e.g., deepseek/deepseek-v4-pro) |
@ai-temperature | 0 to 2 | (none) | AI temperature parameter (provider default when unset) |
@ai-max-tokens | number | (none) | Max tokens for AI response (provider default when unset) |
@ai-key | string | server-configured | AI API key override (default uses the server’s --ai-key flag) |
Example:
// @mode serverless// @ai true// @ai-model deepseek/deepseek-v4-pro// @ai-temperature 0.3
const { text } = await generateText({ model, prompt: `Summarize this: ${req.body.content}`});
return { summary: text };Authentication
Section titled “Authentication”| Comment | Values | Default | Description |
|---|---|---|---|
@token | string | (none) | Per-endpoint shared secret. Requests must provide the token via one of four methods |
Add @token to protect an endpoint, with no middleware, auth library, or configuration file involved:
// @token my-secret-key-123// @cors reflective
return { data: 'only authenticated requests see this' };Clients authenticate using any of these methods (checked in priority order):
| Priority | Method | Example |
|---|---|---|
| 1a | Authorization: Bearer <token> | curl -H "Authorization: Bearer my-secret-key-123" ... |
| 1b | Authorization: Basic (password field) | curl -u user:my-secret-key-123 ... or curl -u :my-secret-key-123 ... |
| 2 | X-Token header | curl -H "X-Token: my-secret-key-123" ... |
| 3 | ?token= query parameter | curl "https://...?token=my-secret-key-123" |
Security details:
- Constant-time comparison (SHA-256 +
timingSafeEqual): immune to timing attacks - The token is redacted in all API responses, logs, and access logs (
[REDACTED]) ?token=is stripped fromreq.urlandmetadata.parametersbefore your script runs- CORS preflight (
OPTIONS) returns204without requiring auth (spec-correct behavior) - WebSocket
upgraderequests are also gated; pass the token via a header or?token= - Empty or whitespace-only values are ignored (endpoint stays public)
See Authentication for the full guide with examples.
Advanced features
Section titled “Advanced features”| Comment | Values | Default | Description |
|---|---|---|---|
@websocket | (flag) | (none) | Enable WebSocket support. Requires worker mode |
@rawBody | (flag) | (none) | Skip the request-body auto-parser, so req stays a raw Node Readable stream (for large uploads, streaming, or passthrough). Accepts @rawBody, @rawBody true, or @rawBody false |
@label | string | (none) | Script classification label for filtering and organization |
@return-type | TypeScript type | (none) | TypeScript type definition for the script’s return value |
@return-type-mode | strict | warn | dev | strict | Enforcement mode for @return-type validation. strict rejects on mismatch, warn logs only, dev enforces only outside production |
@description | text | (none) | API documentation description |
@tags | Tag1, Tag2 | (none) | Categorization tags for API documentation |
Examples:
// @mode worker// @websocket // Enable WebSocket handlers// @label user-api // Classify this script// @return-type { id: string, name: string, email: string }// @return-type-mode strict // Enforce return-type at runtime// @description User profile API// @tags Users, ProfileMagic comments API
Section titled “Magic comments API”Manage magic comments through the API:
| Endpoint | Description |
|---|---|
GET /api/v1/exec/magic-comments/schema | Get the canonical schema of all supported magic comments |
GET /api/v1/exec/magic-comments/read | Read magic comments from a script |
PUT /api/v1/exec/magic-comments/update | Update magic comments on a script |
POST /api/v1/exec/magic-comments/bulk-update | Bulk update magic comments across multiple scripts |
# Read magic comments from a specific scripthoody exec magic-comments read --path "api/hello.ts"// Get the full magic comments schemaconst schema = await containerClient.exec.magic.getSchema();console.log(schema.data);
// Read magic comments from a specific scriptconst comments = await containerClient.exec.magic.read({ path: 'api/hello.ts' });console.log(comments.data); // { path: "api/hello.ts", comments: { mode: "serverless", cors: "reflective", ... } }# Get the full magic comments schemacurl "https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/v1/exec/magic-comments/schema"
# Read magic comments from a specific scriptcurl "https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/v1/exec/magic-comments/read?path=api/hello.ts"One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Gets the canonical schema of supported magic comments, and reads the magic comments currently set on a script.
# Schema
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/v1/exec/magic-comments/schema&method=GET&response=transparent
# Read
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.com/api/v1/exec/magic-comments/read?path=api/hello.ts&method=GET&response=transparent Quick reference
Section titled “Quick reference”All magic comments, by category:
| Category | Comments |
|---|---|
| Execution | @mode, @enabled, @timeout, @await-promises, @concurrent, @schedule |
| Authentication | @token |
| CORS | @cors, @cors-credentials, @cors-methods, @cors-headers, @cors-max-age |
| Logging | @log-level, @debug, @log-request-body, @log-response-body, @log-max-body-size, @log-exclude-headers, @log-retention-days, @debug-instrument |
| AI | @ai, @ai-model, @ai-temperature, @ai-max-tokens, @ai-key |
| Advanced | @websocket, @rawBody, @label, @return-type, @return-type-mode, @description, @tags |