Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .agents/skills/uloop-compile/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Execute Unity project compilation.
## Usage

```bash
uloop compile [--force-recompile] [--no-wait-for-domain-reload] [--stop-on-external-scene-changes]
uloop compile [--force-recompile] [--no-wait-for-domain-reload] [--stop-on-external-scene-changes] [--compile-wait-timeout-seconds <seconds>]
```

## Parameters
Expand All @@ -21,6 +21,7 @@ uloop compile [--force-recompile] [--no-wait-for-domain-reload] [--stop-on-exter
| `--force-recompile` | flag | - | Full recompile plus domain reload. Almost never needed: a plain compile already detects externally edited files, and the forced reload can freeze large projects and come back as `COMPILE_RESULT_UNKNOWN`. |
| `--no-wait-for-domain-reload` | flag | - | Return before Domain Reload completion |
| `--stop-on-external-scene-changes` | flag | - | Stop before compilation if open Scene files changed externally instead of auto-reloading them |
| `--compile-wait-timeout-seconds` | integer | 600 | Maximum seconds the CLI waits for the compile to finish before returning COMPILE_WAIT_TIMEOUT (default 600). Values above 1200 exceed the Unity-side result retention window (20 minutes) and weaken post-timeout recovery. |

## When to use --force-recompile

Expand Down
3 changes: 2 additions & 1 deletion .claude/skills/uloop-compile/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Execute Unity project compilation.
## Usage

```bash
uloop compile [--force-recompile] [--no-wait-for-domain-reload] [--stop-on-external-scene-changes]
uloop compile [--force-recompile] [--no-wait-for-domain-reload] [--stop-on-external-scene-changes] [--compile-wait-timeout-seconds <seconds>]
```

## Parameters
Expand All @@ -21,6 +21,7 @@ uloop compile [--force-recompile] [--no-wait-for-domain-reload] [--stop-on-exter
| `--force-recompile` | flag | - | Full recompile plus domain reload. Almost never needed: a plain compile already detects externally edited files, and the forced reload can freeze large projects and come back as `COMPILE_RESULT_UNKNOWN`. |
| `--no-wait-for-domain-reload` | flag | - | Return before Domain Reload completion |
| `--stop-on-external-scene-changes` | flag | - | Stop before compilation if open Scene files changed externally instead of auto-reloading them |
| `--compile-wait-timeout-seconds` | integer | 600 | Maximum seconds the CLI waits for the compile to finish before returning COMPILE_WAIT_TIMEOUT (default 600). Values above 1200 exceed the Unity-side result retention window (20 minutes) and weaken post-timeout recovery. |

## When to use --force-recompile

Expand Down
7 changes: 7 additions & 0 deletions Packages/src/Editor/FirstPartyTools/Compile/CompileSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ public class CompileSchema : UnityCliLoopToolSchema
/// </summary>
public bool ReloadExternalSceneChanges { get; set; } = true;

/// <summary>
/// How long the CLI waits for compilation to complete, in seconds.
/// Unity ignores this value; it is consumed by the CLI.
/// </summary>
[Description("How long the CLI waits for compilation to complete, in seconds. Unity ignores this value; it is consumed by the CLI.")]
public int CompileWaitTimeoutSeconds { get; set; } = 600;

/// <summary>
/// Internal request identifier used for delayed result recovery across domain reload.
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion Packages/src/Editor/FirstPartyTools/Compile/Skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Execute Unity project compilation.
## Usage

```bash
uloop compile [--force-recompile] [--no-wait-for-domain-reload] [--stop-on-external-scene-changes]
uloop compile [--force-recompile] [--no-wait-for-domain-reload] [--stop-on-external-scene-changes] [--compile-wait-timeout-seconds <seconds>]
```

## Parameters
Expand All @@ -21,6 +21,7 @@ uloop compile [--force-recompile] [--no-wait-for-domain-reload] [--stop-on-exter
| `--force-recompile` | flag | - | Full recompile plus domain reload. Almost never needed: a plain compile already detects externally edited files, and the forced reload can freeze large projects and come back as `COMPILE_RESULT_UNKNOWN`. |
| `--no-wait-for-domain-reload` | flag | - | Return before Domain Reload completion |
| `--stop-on-external-scene-changes` | flag | - | Stop before compilation if open Scene files changed externally instead of auto-reloading them |
| `--compile-wait-timeout-seconds` | integer | 600 | Maximum seconds the CLI waits for the compile to finish before returning COMPILE_WAIT_TIMEOUT (default 600). Values above 1200 exceed the Unity-side result retention window (20 minutes) and weaken post-timeout recovery. |
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## When to use --force-recompile

Expand Down
5 changes: 5 additions & 0 deletions cli/common/tools/default-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
"type": "boolean",
"description": "Stop before compilation if open Scene files changed externally instead of auto-reloading them",
"default": true
},
"CompileWaitTimeoutSeconds": {
"type": "integer",
"description": "Maximum seconds the CLI waits for the compile to finish before returning COMPILE_WAIT_TIMEOUT (default 600). Values above 1200 exceed the Unity-side result retention window (20 minutes) and weaken post-timeout recovery.",
"default": 600
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion cli/dispatcher/shared-inputs-stamp.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"schemaVersion": 1,
"sharedInputsHash": "0313515f1add4e1c394e1957c0aff7215e39e678"
"sharedInputsHash": "9623e4986ab240e4a131ac65128f62aea9da1f99"
}
73 changes: 68 additions & 5 deletions cli/project-runner/internal/projectrunner/compile_wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"math"
"time"

clierrors "github.com/hatayama/unity-cli-loop/common/errors"
Expand All @@ -22,13 +23,21 @@ const (
compileRequestIDParam = "RequestId"
compileWaitParam = clicore.DomainReloadWaitParam
compileForceParam = "ForceRecompile"
compileWaitTimeoutParam = "CompileWaitTimeoutSeconds"
// Why separate from ToolReadinessTimeout (180s): launch readiness stays short.
// Why 10m: worst-case blind block beats headroom. Why ≤ C# CompileResultLifetime (20m):
// timed-out clients can still retrieve results by retrying uloop compile ~10m more.
compileWaitTimeout = 10 * time.Minute
compileWaitPollInterval = clicore.ToolReadinessPoll
compileStatusProbeTimeout = clicore.ToolReadinessProbeTimeout
compileResponseTimeout = 2 * time.Second
compileWaitTimeout = 10 * time.Minute
// Why warn at 20m: Unity stores compile results for CompileResultLifetime (20m).
// Waiting longer does not fail, but a timed-out retry may miss the retained result.
compileWaitTimeoutRetentionWarningSeconds = 1200
// Why: time.Duration is int64 nanoseconds. Values above this overflow to a negative
// duration when multiplied by time.Second, which would look like an immediate timeout.
// This is an overflow guard, not a product-imposed maximum wait.
compileWaitTimeoutMaxSeconds = int64(math.MaxInt64 / int64(time.Second))
compileWaitPollInterval = clicore.ToolReadinessPoll
compileStatusProbeTimeout = clicore.ToolReadinessProbeTimeout
compileResponseTimeout = 2 * time.Second
)

type compileCompletionOptions struct {
Expand Down Expand Up @@ -66,6 +75,59 @@ func prepareCompileWaitParams(params map[string]any) (string, error) {
return requestID, nil
}

// compileWaitTimeoutFromParams reads CompileWaitTimeoutSeconds from tool params.
// Missing values keep the default compileWaitTimeout (10m). Non-positive or non-integer
// values are rejected before a compile request is sent.
func compileWaitTimeoutFromParams(params map[string]any) (time.Duration, error) {
value, exists := params[compileWaitTimeoutParam]
if !exists || value == nil {
return compileWaitTimeout, nil
}

seconds, ok := positiveInt64FromAny(value)
if !ok || seconds > compileWaitTimeoutMaxSeconds {
return 0, clierrors.InvalidValueArgumentError(
"--compile-wait-timeout-seconds",
fmt.Sprint(value),
"positive integer",
)
}
return time.Duration(seconds) * time.Second, nil
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

func positiveInt64FromAny(value any) (int64, bool) {
switch typed := value.(type) {
case int:
if typed <= 0 {
return 0, false
}
return int64(typed), true
case int32:
if typed <= 0 {
return 0, false
}
return int64(typed), true
case int64:
if typed <= 0 {
return 0, false
}
return typed, true
case float64:
if typed <= 0 || typed != math.Trunc(typed) || typed > float64(compileWaitTimeoutMaxSeconds) {
return 0, false
}
return int64(typed), true
case json.Number:
parsed, err := typed.Int64()
if err != nil || parsed <= 0 {
return 0, false
}
return parsed, true
default:
return 0, false
}
}

func ensureCompileRequestID(params map[string]any) (string, error) {
if value, ok := params[compileRequestIDParam].(string); ok && value != "" {
if isSafeCompileRequestID(value) {
Expand Down Expand Up @@ -230,6 +292,7 @@ func logCompileRequestPrepared(
connection unityipc.Connection,
params map[string]any,
requestID string,
waitTimeout time.Duration,
) {
writeCompileVibeLog(connection.ProjectRoot, func() vibelog.CLIVibeLogEntry {
reloadExternalSceneChanges := compileReloadExternalSceneChangesEnabled(params)
Expand All @@ -246,7 +309,7 @@ func logCompileRequestPrepared(
"stop_on_external_scene_changes": !reloadExternalSceneChanges,
"project_identity": vibelog.ProjectIdentity(connection.ProjectRoot),
"endpoint": connection.Endpoint.Address,
"timeout_ms": compileWaitTimeout.Milliseconds(),
"timeout_ms": waitTimeout.Milliseconds(),
"poll_interval_ms": compileWaitPollInterval.Milliseconds(),
"response_timeout_ms": compileResponseTimeout.Milliseconds(),
},
Expand Down
Loading