Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/docs/environment-variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ as per-task auth tokens or workspace paths.
| `AZURE_SANDBOX_EGRESS_INSPECTION` | Optional | Egress proxy TLS inspection: `Partial` (default — only rule-matched traffic inspected), `Full`, `Legacy`, `None`. Use `Full` only with egress rules/transforms; it TLS-resigns all traffic and blocks non-HTTP. |
| `WORKER_RELEASE_CHANNEL` | Optional | Worker release channel, `stable` or `preview`, for hosted worker release selection. |
| `WORKER_RELEASE_VERSION` | Optional | Explicit worker release version for hosted worker bootstrap. |
| `WORKER_REPOSITORY_CLONE_TIMEOUT_SECONDS` | Optional | Maximum time allowed for an initial repository clone. Defaults to `600`; increase this for large repositories or slower networks. |
| `LOCAL_SANDBOX_FILES_DIR` | Local development | Local override for sandbox bootstrap files. Used only by development workflows. |

### Source control providers
Expand Down
4 changes: 4 additions & 0 deletions apps/docs/providers/compute/docker.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ one to a fix.
they include the underlying crash, missing dependency, or fetch failure.
- **The task cannot reach Roomote services.** Check `DOCKER_WORKER_NETWORK` and
the Compose network used by the API and controller.
- **A large repository times out while cloning.** Increase
`WORKER_REPOSITORY_CLONE_TIMEOUT_SECONDS` from its 600-second default and
restart the Roomote services. Timeout failures stop immediately instead of
restarting the same clone from zero.
- **A worker reports that its disk limit is unsupported.** Configure a Docker
storage driver with per-container writable-layer quota support. Only set
`DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=true` when an equivalent host-level quota
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 17 additions & 3 deletions apps/worker/src/command-executor/command-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface ExecutionResult {
stdout?: string;
stderr?: string;
error?: string;
timedOut?: boolean;
}

export class ExecutionError extends Error {
Expand All @@ -56,15 +57,25 @@ export class ExecutionError extends Error {
formatDetails(): string {
const { result } = this;

const truncate = (s: string, max = 4000) =>
s.length > max ? '... (truncated)\n' + s.slice(-max) : s;
const sanitize = (value: string) =>
value.replace(/(https?:\/\/)[^\s/@]+(?::[^\s/@]*)?@/gi, '$1[redacted]@');
const truncate = (s: string, max = 4000) => {
const sanitized = sanitize(s);
return sanitized.length > max
? '... (truncated)\n' + sanitized.slice(-max)
: sanitized;
};

const parts = [result.command.run];
const parts = [sanitize(result.command.run)];

if (result.exitCode !== undefined) {
parts.push(`\nexit code -> ${result.exitCode}`);
}

if (result.timedOut) {
parts.push(`\ntimeout -> ${result.command.timeout} seconds`);
}

if (result.error) {
parts.push(`\nerror -> ${truncate(result.error)}`);
}
Expand Down Expand Up @@ -104,6 +115,7 @@ export class CommandExecutor {
let duration;
let error;
let exitCode: number | undefined;
let timedOut = false;
let stderr = '';
let stdout = '';

Expand Down Expand Up @@ -178,6 +190,7 @@ export class CommandExecutor {
if (e instanceof ExecaError) {
error = e.shortMessage;
exitCode = e.exitCode;
timedOut = e.timedOut;

if (e.stdout) {
if (verbose) {
Expand Down Expand Up @@ -209,6 +222,7 @@ export class CommandExecutor {
stdout,
stderr,
error,
timedOut,
};

if (!success && !command.continue_on_error) {
Expand Down
1 change: 1 addition & 0 deletions apps/worker/src/commands/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export async function setup({
...workspaceOpts,
cleanupLegacyPaths:
workspaceOpts.taskRunType === TaskPayloadKind.SnapshotEnvironment,
repositoryCloneTimeoutSeconds: workerEnv.repositoryCloneTimeoutSeconds,
envVars: {
...workerEnv.buildUserFacingEnv(),
...workspaceOpts.envVars,
Expand Down
2 changes: 2 additions & 0 deletions apps/worker/src/commands/setup/workspace/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export async function initializeRepositories(
gitAuthorName,
gitAuthorEmail,
sourceControlProvider,
repositoryCloneTimeoutSeconds,
}: PrepareWorkspaceOptions,
): Promise<PrepareWorkspaceResult> {
const resolvedSourceControlProvider =
Expand All @@ -81,6 +82,7 @@ export async function initializeRepositories(
const { workspaceRoot, workspaceManager } = createWorkspaceManager(
envVars,
logger,
repositoryCloneTimeoutSeconds,
);

await timedStep(logger, 'initializeRepositories: configure git', () =>
Expand Down
2 changes: 2 additions & 0 deletions apps/worker/src/commands/setup/workspace/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export function resolveRuntimePathsForWorker() {
export function createWorkspaceManager(
envVars: Record<string, string | undefined>,
logger?: StartupLogger,
repositoryCloneTimeoutSeconds?: number,
): {
workspaceRoot: string;
workspaceManager: WorkspaceManager;
Expand All @@ -53,6 +54,7 @@ export function createWorkspaceManager(
logger
? (label, fn) => timedStep(logger, `initializeRepositories: ${label}`, fn)
: undefined,
{ repositoryCloneTimeoutSeconds },
);

return { workspaceRoot, workspaceManager };
Expand Down
2 changes: 2 additions & 0 deletions apps/worker/src/commands/setup/workspace/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ export interface PrepareWorkspaceOptions {
serviceContext?: ServiceContext;
gitAuthorName?: string;
gitAuthorEmail?: string;
/** Deployment-managed clone deadline; never exposed to project commands. */
repositoryCloneTimeoutSeconds?: number;
}

function formatWorkspaceRepositoryPreparationMessage(
Expand Down
21 changes: 21 additions & 0 deletions apps/worker/src/env/__tests__/worker-env.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 27 additions & 1 deletion apps/worker/src/env/worker-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as os from 'node:os';

import { configureAuthClientEnv } from '@roomote/auth/client';
import {
COMMAND_DEFAULT_TIMEOUT,
DEFAULT_MODEL_PROVIDER_ENV_KEYS,
OPENCODE_AUTH_CONTENT_ENV_VAR_NAME,
parseModelProviderEnvKeys,
Expand All @@ -21,6 +22,7 @@ interface WorkerConfig {
previewAuthPublicKey?: string;
previewAuthCookieName?: string;
appEnv?: string;
repositoryCloneTimeoutSeconds?: number;
}

const PRESET_SYSTEM_ENV: Record<string, string> = {
Expand All @@ -45,7 +47,12 @@ const SYSTEM_KEYS = [
// so nested application commands do not inherit it accidentally. This includes
// the legacy ROOMOTE_APP_ENV alias the controller still injects for pre-rename
// snapshot workers.
const WORKER_INTERNAL_CONFIG_KEYS = ['R_APP_ENV', 'APP_ENV', 'ROOMOTE_APP_ENV'];
const WORKER_INTERNAL_CONFIG_KEYS = [
'R_APP_ENV',
'APP_ENV',
'ROOMOTE_APP_ENV',
'WORKER_REPOSITORY_CLONE_TIMEOUT_SECONDS',
];
const BLOCKED_USER_FACING_ENV_KEYS = new Set([
'AUTH_TOKEN',
'TRPC_URL',
Expand Down Expand Up @@ -111,6 +118,15 @@ function buildLauncherOpenCodeEnv(
return env;
}

function parsePositiveInteger(
value: string | undefined,
fallback: number,
): number {
const parsed = Number(value);

return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
}

/**
* Centralized environment manager for the worker.
*
Expand Down Expand Up @@ -224,6 +240,10 @@ export class WorkerEnv {
previewAuthCookieName: processEnv.PREVIEW_AUTH_COOKIE_NAME,
roomoteAppUrl: processEnv.R_APP_URL!,
appEnv: processEnv.R_APP_ENV ?? processEnv.APP_ENV,
repositoryCloneTimeoutSeconds: parsePositiveInteger(
processEnv.WORKER_REPOSITORY_CLONE_TIMEOUT_SECONDS,
COMMAND_DEFAULT_TIMEOUT,
),
};

const env = new WorkerEnv({
Expand Down Expand Up @@ -405,4 +425,10 @@ export class WorkerEnv {
get appEnv(): string | undefined {
return this.workerConfig.appEnv;
}

get repositoryCloneTimeoutSeconds(): number {
return (
this.workerConfig.repositoryCloneTimeoutSeconds ?? COMMAND_DEFAULT_TIMEOUT
);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading