Skip to content

Fix/result types atomic add - #2

Merged
vsraccubits merged 5 commits into
mainfrom
fix/result-types-atomic-add
Oct 19, 2026
Merged

vsraccubits merged 5 commits into
mainfrom
fix/result-types-atomic-add

Conversation

@vsraccubits

@vsraccubits vsraccubits commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes how taskgraph stores and shows task results, and makes task creation all-or-nothing when a dependency is wrong.

  • A task with a numeric result or metadata could not be read back, so show, list, go and done --next failed for every project holding one.
  • Upstream results that were not strings were hidden in go and done output, and long non-ASCII results could crash both commands.
  • task add and batch create with an unknown dependency failed only after the task was saved, leaving a half-created task behind.

Changes

Read numeric JSON columns as text (c3e020c)

Columns declared as JSON get NUMERIC type affinity in SQLite, so a result such as 500500 or 6.92 is stored as INTEGER or REAL. Reading it back as String failed with an invalid column type error, which broke get_task, list_tasks and handoff context.

A new json_text helper in src/db/mod.rs reads INTEGER, REAL, TEXT and BLOB values as text. Every JSON column read now uses it: task result, metadata and sleep state ref; process run and task wait state refs; artifact, dependency and project metadata; and event payload.

Show non-string handoff results (0ba5523)

In the upstream section of go and done --next:

  • Numbers, booleans and objects are shown as compact JSON. Before, done printed (no result) and go printed (structured).
  • Results over 120 characters are cut by character instead of by byte. Byte slicing panicked when the cut fell inside a multi-byte character.

Keep scalar done --result values as written (6c35c92)

done --result now parses the text as JSON only when it is an object or an array. Anything else is stored as the string passed, so 6.90 stays 6.90 instead of becoming 6.9.

Create tasks and their dependencies atomically (5b399fc)

task add saved the task, then added each --dep in a separate statement. Batch create committed the tasks, then added edges and tags outside the transaction. An unknown dependency id failed with FOREIGN KEY constraint failed after the task was already saved.

  • New create_task_with_deps and batch_create_tasks_with_edges insert tasks, tags and edges in one transaction.
  • Each edge checks that its upstream task exists. If it does not, the call fails with an error naming the id and nothing is saved.
  • create_task and batch_create_tasks delegate to the new functions, and the task INSERT that was duplicated between them moves into insert_task_row.

Add .gitignore (238e87a)

GitHub's Rust template, plus local taskgraph state: *.db with its -wal, -shm and -journal files, .taskgraph/, and .env* except .env.example.

Behaviour changes

  • taskgraph done --result 500500 now stores the string "500500", not the number. The same applies to true, false and null, so --result null no longer clears the result. Objects and arrays are still stored as JSON. Code that passes a serde_json::Value straight to complete_task is unaffected.
  • taskgraph add --dep <unknown id> now fails with not found: dependency task <id>; nothing was created. Check the id with `taskgraph list` and leaves no task behind.

Testing

  • cargo test: 28 passed, no warnings. New unit tests in src/db/tests.rs:
    • scalar_and_object_results_read_back_and_hand_off
    • numeric_metadata_reads_back
    • add_with_unknown_dependency_creates_nothing
    • batch_with_unknown_dependency_creates_nothing
  • tests/functional_test.sh: 147 passed, 0 failed. The new RESULT TYPES and ATOMIC ADD sections cover numeric, float, object and long non-ASCII results through done --next, and add with an unknown dependency.

Not in this PR

  • insert_task_between, pivot_subtree, split_task and decompose/replan still create tasks and add edges in separate steps.
  • The HTTP batch endpoint still drops each task's tags. batch_create_tasks_with_edges now accepts tags, so it could pass them through in a follow-up.

History

These five commits were first pushed straight to main (37b8429..9042c2e) and then reverted there (594f7f8..5bdfaab). This PR brings them back through review. Reverted main has the same code as ffc8dfd, so the PR diff contains the full change.

🤖 Generated with Claude Code

vsraccubits and others added 5 commits October 19, 2026 13:19
Columns declared as JSON get NUMERIC affinity in SQLite, so scalar
results and metadata such as 500500 or 6.92 are stored as INTEGER or
REAL. Reading them back as String failed, breaking get_task, list_tasks
and handoff context for tasks with numeric results.

Add a json_text helper that reads INTEGER, REAL, TEXT and BLOB values
as text, and use it for every JSON column read (task result, metadata,
sleep state refs, process/wait state refs, artifact, dependency, event
and project JSON fields). Add tests covering scalar and object results
through completion and handoff, and numeric task metadata.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The upstream handoff display only rendered string results: done printed
"(no result)" for numeric, boolean or object results, and go printed
"(structured)". Both truncated long results by byte index, which
panics when the cut lands inside a multi-byte UTF-8 character.

Render non-string results as compact JSON, keep "(no result)" for
null, and truncate by characters instead of bytes. Add functional
tests for numeric, object and long non-ASCII results handed off
through done --next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
done --result parsed any text that was valid JSON, so scalar results
lost their original form: 6.90 was stored as 6.9, and "null" cleared
the result entirely.

Only parse objects and arrays as JSON; store everything else as the
string the caller passed. Add functional tests that a float result
keeps its text in done output and in show.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
task add and batch creation inserted the task rows first and added
dependencies afterwards in separate statements. An unknown dependency
id failed the command after the task was already written, leaving an
orphan task with partial edges and tags behind.

Add create_task_with_deps and batch_create_tasks_with_edges, which
insert tasks, tags and dependency edges in one transaction. Each edge
checks that its upstream task exists and fails with a NotFound error
naming the id, which rolls back the whole call. create_task and
batch_create_tasks delegate to the new functions, and the shared
INSERT_TASK binding moves into insert_task_row.

Add unit tests that single and batch creation with an unknown
dependency create nothing, and a functional test for task add.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ignore Cargo build output and Rust tooling artifacts (rustfmt
backups, MSVC debug info, cargo-mutants output, rustc ICE dumps),
plus local taskgraph state: SQLite databases with their WAL, SHM and
journal files, the .taskgraph/ directory, and .env files other than
.env.example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vsraccubits
vsraccubits changed the base branch from main to feature/runtime_integration October 19, 2026 07:57
@vsraccubits
vsraccubits changed the base branch from feature/runtime_integration to main October 19, 2026 07:57
@vsraccubits
vsraccubits merged commit 679ae68 into main Oct 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant