From 3054c8651f4d4d038ad4cd01d8d3ede9fe42ea31 Mon Sep 17 00:00:00 2001 From: charlie0228 <24387287+charlie0228@users.noreply.github.com> Date: Mon, 5 Oct 2026 03:12:36 +0000 Subject: [PATCH 1/5] docs(cron): document thread_id/id interaction and fix empty thread_id examples --- docs/config-reference.md | 2 +- docs/cronjob.md | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/config-reference.md b/docs/config-reference.md index 838ceacb6..d4715854f 100644 --- a/docs/config-reference.md +++ b/docs/config-reference.md @@ -790,7 +790,7 @@ message = "summarize yesterday's merged PRs" # message sent to agent platform = "discord" # optional, default: "discord" sender_name = "DailyOps" # optional, default: "openab-cron" timezone = "America/New_York" # optional, default: "UTC" -thread_id = "" # optional, post to existing thread +# thread_id = "123456789" # optional, post to existing thread (omit for a new thread per run; never "") [[cron.jobs]] schedule = "0 0 * * 0" diff --git a/docs/cronjob.md b/docs/cronjob.md index 4aa0231ad..072ada65e 100644 --- a/docs/cronjob.md +++ b/docs/cronjob.md @@ -37,7 +37,7 @@ message = "summarize yesterday's merged PRs" # required: prompt for the agent platform = "discord" # optional, default: "discord" sender_name = "DailyOps" # optional, default: "openab-cron" timezone = "America/New_York" # optional, default: "UTC" -thread_id = "" # optional: post to existing thread +# thread_id = "123456789012345678" # optional: post to existing thread (omit to create a new thread per run) ``` | Field | Required | Default | Description | @@ -49,7 +49,21 @@ thread_id = "" # optional: post to existing thread | `platform` | | `"discord"` | `"discord"`, `"slack"`, `"telegram"`, `"googlechat"`, or `"lineworks"` (non-default platforms require their feature) | | `sender_name` | | `"openab-cron"` | Attribution shown in prompt context | | `timezone` | | `"UTC"` | IANA timezone (e.g. `"America/New_York"`, `"Europe/Berlin"`) | -| `thread_id` | | — | Post into an existing thread instead of the channel | +| `thread_id` | | — | Post into an existing thread instead of creating a new one. Omit the field entirely to create a new thread per run — do not set it to `""` (see [Thread Behavior](#thread-behavior)) | +| `id` | | — | Usercron only. Stable job identifier, required for `disable_on_success`. Enables scheduler writeback, including persisting an auto-created `thread_id` (see [Thread Behavior](#thread-behavior)) | + +### Thread Behavior + +Where a job posts depends on `thread_id` and, for usercron jobs, `id`. This applies to platforms with thread support; Google Chat and LINE WORKS differ (see [Platform Prerequisites](#platform-prerequisites)). + +| `thread_id` | `id` | Behavior | +|---|---|---| +| set | any | Every run posts into that thread. No new thread, no writeback. | +| omitted | omitted | Every run creates a new thread. Since sessions are keyed by thread, each run also starts a fresh agent session. | +| omitted | set (usercron) | The first run creates a thread and the scheduler writes its ID back to `cronjob.toml` as `thread_id`. All later runs post into that same thread. | +| `""` | any | Invalid. An empty string is treated as a thread ID, not as unset. On Discord the send fails (`failed to send cron message ... cannot parse integer from empty string` in the logs) and nothing is posted. | + +`id` does not select a thread — it only tells the scheduler which entry to update. If you remove a written-back `thread_id` but keep `id`, the next run creates a new thread and pins the job to it again. To get a new thread on every run, omit both fields. ## Cron Expression Format @@ -326,7 +340,7 @@ usercron_path = "cronjob.toml" - **Minute-aligned**: The scheduler aligns to minute boundaries (`:00`), so `0 9 * * *` fires at exactly 09:00:00, not at whatever second the process started. - **Overlap protection**: If a previous execution of the same job is still running, the next tick is skipped. - **Isolation**: Cron failures are logged but never block interactive chat traffic. -- **Usercron persistence**: For usercron jobs, the scheduler may write `thread_id` and `enabled = false` back to `cronjob.toml`. +- **Usercron persistence**: For usercron jobs with an `id`, the scheduler may write `thread_id` and `enabled = false` back to `cronjob.toml` (see [Thread Behavior](#thread-behavior)). - **Graceful shutdown**: In-flight cron tasks are waited on (up to 30 seconds) during shutdown. ## Sender Identity From 0ce3a0fe6620eb01543772c9a794c57ee5bf1ae3 Mon Sep 17 00:00:00 2001 From: chaodu-agent Date: Mon, 5 Oct 2026 20:56:30 +0000 Subject: [PATCH 2/5] docs(cron): address #1561 review findings - Thread Behavior: 'no thread_id writeback' (enabled=false can still be written back), qualify the pinned-thread row on successful writeback and link the protected-file caveat, mark '""' invalid on every platform - drop the sentence duplicating the 'omitted/omitted' row - config-reference: thread_id default is unset, not ""; baseline rejects only disable_on_success, other usercron-only fields are ignored - note id is ignored in baseline [[cron.jobs]] - use 18-digit example IDs consistently --- docs/config-reference.md | 10 +++++----- docs/cronjob.md | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/config-reference.md b/docs/config-reference.md index d4715854f..8fa4d912c 100644 --- a/docs/config-reference.md +++ b/docs/config-reference.md @@ -785,16 +785,16 @@ usercron_path = "cronjob.toml" # relative to $HOME/.openab/, or ab [[cron.jobs]] enabled = true # optional, default: true schedule = "0 9 * * 1-5" # cron expression (5-field POSIX) -channel = "123456789" # target channel/thread ID +channel = "123456789012345678" # target channel/thread ID message = "summarize yesterday's merged PRs" # message sent to agent platform = "discord" # optional, default: "discord" sender_name = "DailyOps" # optional, default: "openab-cron" timezone = "America/New_York" # optional, default: "UTC" -# thread_id = "123456789" # optional, post to existing thread (omit for a new thread per run; never "") +# thread_id = "123456789012345678" # optional, post to existing thread (omit for a new thread per run; never "") [[cron.jobs]] schedule = "0 0 * * 0" -channel = "123456789" +channel = "123456789012345678" message = "generate weekly status report" platform = "discord" timezone = "UTC" @@ -818,13 +818,13 @@ timezone = "UTC" | `platform` | string | `"discord"` | Target platform (`"discord"` or `"slack"`). | | `sender_name` | string | `"openab-cron"` | Sender attribution shown in the prompt context. | | `timezone` | string | `"UTC"` | IANA timezone for schedule evaluation (e.g. `"America/New_York"`, `"Europe/Berlin"`). | -| `thread_id` | string | `""` | Optional thread ID to post into an existing thread. | +| `thread_id` | string | — (unset) | Optional thread ID to post into an existing thread. Omit the field for a new thread per run; never set it to `""` (see [Thread Behavior](cronjob.md#thread-behavior)). | The external `cronjob.toml` uses `[[jobs]]` (same fields). See [Usercron docs](cronjob.md#usercron--hot-reload-with-cronjobtoml) for details. ### Usercron-only `[[jobs]]` fields -These fields are valid only in the external usercron file, for example `$HOME/.openab/cronjob.toml`. They are rejected in baseline `[[cron.jobs]]` because OpenAB only writes state back to the user-managed cron file. +These fields are valid only in the external usercron file, for example `$HOME/.openab/cronjob.toml`. `disable_on_success` is rejected in baseline `[[cron.jobs]]`; the other fields, including `id`, are accepted there but ignored, because OpenAB only writes state back to the user-managed cron file. | Key | Type | Default | Description | |-----|------|---------|-------------| diff --git a/docs/cronjob.md b/docs/cronjob.md index 072ada65e..dbab9c722 100644 --- a/docs/cronjob.md +++ b/docs/cronjob.md @@ -50,7 +50,7 @@ timezone = "America/New_York" # optional, default: "UTC" | `sender_name` | | `"openab-cron"` | Attribution shown in prompt context | | `timezone` | | `"UTC"` | IANA timezone (e.g. `"America/New_York"`, `"Europe/Berlin"`) | | `thread_id` | | — | Post into an existing thread instead of creating a new one. Omit the field entirely to create a new thread per run — do not set it to `""` (see [Thread Behavior](#thread-behavior)) | -| `id` | | — | Usercron only. Stable job identifier, required for `disable_on_success`. Enables scheduler writeback, including persisting an auto-created `thread_id` (see [Thread Behavior](#thread-behavior)) | +| `id` | | — | Usercron only; ignored in baseline `[[cron.jobs]]`. Stable job identifier, required for `disable_on_success`. Enables scheduler writeback, including persisting an auto-created `thread_id` (see [Thread Behavior](#thread-behavior)) | ### Thread Behavior @@ -58,12 +58,12 @@ Where a job posts depends on `thread_id` and, for usercron jobs, `id`. This appl | `thread_id` | `id` | Behavior | |---|---|---| -| set | any | Every run posts into that thread. No new thread, no writeback. | +| set | any (ignored) | Every run posts into that thread. No new thread and no `thread_id` writeback. | | omitted | omitted | Every run creates a new thread. Since sessions are keyed by thread, each run also starts a fresh agent session. | -| omitted | set (usercron) | The first run creates a thread and the scheduler writes its ID back to `cronjob.toml` as `thread_id`. All later runs post into that same thread. | -| `""` | any | Invalid. An empty string is treated as a thread ID, not as unset. On Discord the send fails (`failed to send cron message ... cannot parse integer from empty string` in the logs) and nothing is posted. | +| omitted | set (usercron) | The first run creates a thread and the scheduler writes its ID back to `cronjob.toml` as `thread_id`. If that writeback succeeds, all later runs post into that same thread. If OpenAB cannot write the file, a new thread is created on every run (see [Choosing the right scope](#choosing-the-right-scope)). | +| `""` | any | Invalid on every platform. An empty string is treated as a thread ID, not as unset. On Discord, for example, the send fails (`failed to send cron message ... cannot parse integer from empty string` in the logs) and nothing is posted. | -`id` does not select a thread — it only tells the scheduler which entry to update. If you remove a written-back `thread_id` but keep `id`, the next run creates a new thread and pins the job to it again. To get a new thread on every run, omit both fields. +`id` does not select a thread — it only tells the scheduler which entry to update. If you remove a written-back `thread_id` but keep `id`, the next run creates a new thread and pins the job to it again. ## Cron Expression Format From c93d5b1882e1af63793755cc7354e19d1bc49514 Mon Sep 17 00:00:00 2001 From: chaodu-agent Date: Mon, 5 Oct 2026 21:00:43 +0000 Subject: [PATCH 3/5] docs(cron): address remaining #1561 review findings - field table: omitting thread_id with a usercron id pins the job to its first thread; id must be non-empty and unique (writeback updates the first match), empty id counts as unset - replace the commented thread_id = "" in config.toml.example - add a Troubleshooting row for the empty thread_id symptom - use a thread_id example distinct from the channel example --- config.toml.example | 2 +- docs/config-reference.md | 2 +- docs/cronjob.md | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/config.toml.example b/config.toml.example index e5cf7df3d..fe3d7ac6d 100644 --- a/config.toml.example +++ b/config.toml.example @@ -339,7 +339,7 @@ error_hold_ms = 2500 # platform = "discord" # "discord" or "slack" # sender_name = "DailyOps" # attribution (default: "openab-cron") # timezone = "America/New_York" # IANA timezone (default: "UTC") -# thread_id = "" # optional: post to existing thread +# thread_id = "234567890123456789" # optional: post to existing thread (omit for a new thread; never "") # [[cron.jobs]] # schedule = "0 0 * * 0" diff --git a/docs/config-reference.md b/docs/config-reference.md index 8fa4d912c..37a1bc36c 100644 --- a/docs/config-reference.md +++ b/docs/config-reference.md @@ -790,7 +790,7 @@ message = "summarize yesterday's merged PRs" # message sent to agent platform = "discord" # optional, default: "discord" sender_name = "DailyOps" # optional, default: "openab-cron" timezone = "America/New_York" # optional, default: "UTC" -# thread_id = "123456789012345678" # optional, post to existing thread (omit for a new thread per run; never "") +# thread_id = "234567890123456789" # optional, post to existing thread (omit for a new thread per run; never "") [[cron.jobs]] schedule = "0 0 * * 0" diff --git a/docs/cronjob.md b/docs/cronjob.md index dbab9c722..99f4363d2 100644 --- a/docs/cronjob.md +++ b/docs/cronjob.md @@ -37,7 +37,7 @@ message = "summarize yesterday's merged PRs" # required: prompt for the agent platform = "discord" # optional, default: "discord" sender_name = "DailyOps" # optional, default: "openab-cron" timezone = "America/New_York" # optional, default: "UTC" -# thread_id = "123456789012345678" # optional: post to existing thread (omit to create a new thread per run) +# thread_id = "234567890123456789" # optional: post to existing thread (omit to create a new thread; never "") ``` | Field | Required | Default | Description | @@ -49,8 +49,8 @@ timezone = "America/New_York" # optional, default: "UTC" | `platform` | | `"discord"` | `"discord"`, `"slack"`, `"telegram"`, `"googlechat"`, or `"lineworks"` (non-default platforms require their feature) | | `sender_name` | | `"openab-cron"` | Attribution shown in prompt context | | `timezone` | | `"UTC"` | IANA timezone (e.g. `"America/New_York"`, `"Europe/Berlin"`) | -| `thread_id` | | — | Post into an existing thread instead of creating a new one. Omit the field entirely to create a new thread per run — do not set it to `""` (see [Thread Behavior](#thread-behavior)) | -| `id` | | — | Usercron only; ignored in baseline `[[cron.jobs]]`. Stable job identifier, required for `disable_on_success`. Enables scheduler writeback, including persisting an auto-created `thread_id` (see [Thread Behavior](#thread-behavior)) | +| `thread_id` | | — | Post into an existing thread instead of creating a new one. Omit the field to create a new thread (a new one per run, unless a usercron `id` pins the job to its first thread); do not set it to `""` (see [Thread Behavior](#thread-behavior)) | +| `id` | | — | Usercron only; ignored in baseline `[[cron.jobs]]`. Non-empty, unique job identifier, required for `disable_on_success`. Enables scheduler writeback, including persisting an auto-created `thread_id` (see [Thread Behavior](#thread-behavior)). An empty `id` counts as unset; with duplicate IDs, writeback updates only the first matching entry | ### Thread Behavior @@ -404,3 +404,4 @@ See [Kubernetes CronJob Reference Architecture](cronjob_k8s_refarch.md) for the | Usercron not reloading | File not saved / wrong path | Check logs for `usercron file changed, reloading` | | Usercron parse error | Invalid TOML syntax | Check logs for `failed to parse usercron file` | | Goal job does not auto-disable | Command did not exit `0` or output did not include `disable_on_success_match` | Run the command manually and confirm both conditions | +| Nothing posted; logs show `failed to send cron message ... cannot parse integer from empty string` | `thread_id = ""` | Remove the `thread_id` line or set a real thread ID (see [Thread Behavior](#thread-behavior)) | From 1e615615b72e1e4b9556c3692be7f5c507bd24f9 Mon Sep 17 00:00:00 2001 From: chaodu-agent Date: Mon, 5 Oct 2026 21:02:09 +0000 Subject: [PATCH 4/5] docs(cron): frame empty thread_id as a known defect, note reload delay Describe thread_id = "" as a known defect rather than a rule (only the Discord failure is verified; a future release may treat it as unset), and note that a written-back thread_id takes effect after the next usercron reload (about a minute). --- docs/cronjob.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cronjob.md b/docs/cronjob.md index 99f4363d2..e3dce44a1 100644 --- a/docs/cronjob.md +++ b/docs/cronjob.md @@ -60,8 +60,8 @@ Where a job posts depends on `thread_id` and, for usercron jobs, `id`. This appl |---|---|---| | set | any (ignored) | Every run posts into that thread. No new thread and no `thread_id` writeback. | | omitted | omitted | Every run creates a new thread. Since sessions are keyed by thread, each run also starts a fresh agent session. | -| omitted | set (usercron) | The first run creates a thread and the scheduler writes its ID back to `cronjob.toml` as `thread_id`. If that writeback succeeds, all later runs post into that same thread. If OpenAB cannot write the file, a new thread is created on every run (see [Choosing the right scope](#choosing-the-right-scope)). | -| `""` | any | Invalid on every platform. An empty string is treated as a thread ID, not as unset. On Discord, for example, the send fails (`failed to send cron message ... cannot parse integer from empty string` in the logs) and nothing is posted. | +| omitted | set (usercron) | The first run creates a thread and the scheduler writes its ID back to `cronjob.toml` as `thread_id`. If that writeback succeeds, later runs post into that same thread once the scheduler reloads the file (about a minute). If OpenAB cannot write the file, a new thread is created on every run (see [Choosing the right scope](#choosing-the-right-scope)). | +| `""` | any | Known defect; do not use. An empty string is not treated as unset: it is passed through as a thread ID. On Discord the send fails (`failed to send cron message ... cannot parse integer from empty string` in the logs) and nothing is posted; other platforms may behave differently. A future release may treat `""` as unset. | `id` does not select a thread — it only tells the scheduler which entry to update. If you remove a written-back `thread_id` but keep `id`, the next run creates a new thread and pins the job to it again. From 5bf317b535056ab03af3d5acf526ee2fdc911e5c Mon Sep 17 00:00:00 2001 From: chaodu-agent Date: Mon, 5 Oct 2026 21:05:20 +0000 Subject: [PATCH 5/5] docs(cron): tighten thread/id wording per re-review - matrix: id column 'any' (id still drives enabled=false writeback); qualify pinning on a successful thread creation that records a real thread ID; replace the anchor that does not exist on this base - config-reference: omitting thread_id pins with a usercron id; id must be non-empty and unique - config.toml.example: 18-digit channel example --- config.toml.example | 2 +- docs/config-reference.md | 4 ++-- docs/cronjob.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config.toml.example b/config.toml.example index fe3d7ac6d..003545fb6 100644 --- a/config.toml.example +++ b/config.toml.example @@ -334,7 +334,7 @@ error_hold_ms = 2500 # [[cron.jobs]] # enabled = true # optional, default: true # schedule = "0 9 * * 1-5" # weekdays at 9:00 AM -# channel = "123456789" # target channel/thread ID +# channel = "123456789012345678" # target channel/thread ID # message = "summarize yesterday's merged PRs" # prompt for the agent # platform = "discord" # "discord" or "slack" # sender_name = "DailyOps" # attribution (default: "openab-cron") diff --git a/docs/config-reference.md b/docs/config-reference.md index 37a1bc36c..bb1476091 100644 --- a/docs/config-reference.md +++ b/docs/config-reference.md @@ -818,7 +818,7 @@ timezone = "UTC" | `platform` | string | `"discord"` | Target platform (`"discord"` or `"slack"`). | | `sender_name` | string | `"openab-cron"` | Sender attribution shown in the prompt context. | | `timezone` | string | `"UTC"` | IANA timezone for schedule evaluation (e.g. `"America/New_York"`, `"Europe/Berlin"`). | -| `thread_id` | string | — (unset) | Optional thread ID to post into an existing thread. Omit the field for a new thread per run; never set it to `""` (see [Thread Behavior](cronjob.md#thread-behavior)). | +| `thread_id` | string | — (unset) | Optional thread ID to post into an existing thread. Omit the field for a new thread (one per run, unless a usercron `id` pins the job to its first thread); never set it to `""` (see [Thread Behavior](cronjob.md#thread-behavior)). | The external `cronjob.toml` uses `[[jobs]]` (same fields). See [Usercron docs](cronjob.md#usercron--hot-reload-with-cronjobtoml) for details. @@ -828,7 +828,7 @@ These fields are valid only in the external usercron file, for example `$HOME/.o | Key | Type | Default | Description | |-----|------|---------|-------------| -| `id` | string | *required with `disable_on_success`* | Stable job ID used when the scheduler writes `enabled = false` or `thread_id` back to `cronjob.toml`. | +| `id` | string | *required with `disable_on_success`* | Non-empty, unique job ID used when the scheduler writes `enabled = false` or `thread_id` back to `cronjob.toml`. An empty `id` counts as unset; with duplicate IDs, writeback updates only the first matching entry. | | `disable_on_success` | string | — | Command to run before sending the scheduled prompt. | | `disable_on_success_match` | string | *required with `disable_on_success`* | Marker that must appear in stdout or stderr, in addition to exit code `0`, before the job is considered complete. | | `disable_on_success_timeout_secs` | integer | `60` | Timeout for the completion check command. | diff --git a/docs/cronjob.md b/docs/cronjob.md index e3dce44a1..9f9fce524 100644 --- a/docs/cronjob.md +++ b/docs/cronjob.md @@ -58,9 +58,9 @@ Where a job posts depends on `thread_id` and, for usercron jobs, `id`. This appl | `thread_id` | `id` | Behavior | |---|---|---| -| set | any (ignored) | Every run posts into that thread. No new thread and no `thread_id` writeback. | +| set | any | Every run posts into that thread. No new thread and no `thread_id` writeback. | | omitted | omitted | Every run creates a new thread. Since sessions are keyed by thread, each run also starts a fresh agent session. | -| omitted | set (usercron) | The first run creates a thread and the scheduler writes its ID back to `cronjob.toml` as `thread_id`. If that writeback succeeds, later runs post into that same thread once the scheduler reloads the file (about a minute). If OpenAB cannot write the file, a new thread is created on every run (see [Choosing the right scope](#choosing-the-right-scope)). | +| omitted | set (usercron) | The first run that successfully creates a thread has the scheduler write its ID back to `cronjob.toml` as `thread_id`. If that writeback succeeds and records a real thread ID, later runs post into that same thread once the scheduler reloads the file (about a minute). If OpenAB cannot write the file, a new thread is created on every run (see "Choosing the right scope" under Autonomy and Permission Scope below). | | `""` | any | Known defect; do not use. An empty string is not treated as unset: it is passed through as a thread ID. On Discord the send fails (`failed to send cron message ... cannot parse integer from empty string` in the logs) and nothing is posted; other platforms may behave differently. A future release may treat `""` as unset. | `id` does not select a thread — it only tells the scheduler which entry to update. If you remove a written-back `thread_id` but keep `id`, the next run creates a new thread and pins the job to it again.